From af6dfbac8cb0f4c038a5bd6ccf349b6c6ed4476a Mon Sep 17 00:00:00 2001
From: acite <1498045907@qq.com>
Date: Sun, 5 Oct 2025 03:00:26 +0800
Subject: [PATCH] [feat] Ctl framework
---
.gitignore | 4 +-
.../.idea/dictionaries/project.xml | 7 +
.idea/.idea.Abyss/.idea/workspace.xml | 121 +++++++++++-------
Abyss.sln | 6 +
Abyss.sln.DotSettings.user | 1 +
Abyss/Abyss.csproj | 4 +
.../Controllers/Security/UserController.cs | 2 +-
.../Controllers/Task/TaskController.cs | 45 ++++---
.../Services/Admin/Attributes/Module.cs | 29 +++++
Abyss/Components/Services/Admin/CtlService.cs | 110 ++++++++++++++++
.../Services/Admin/Interfaces/IModule.cs | 8 ++
.../Services/Admin/Modules/HelloModule.cs | 18 +++
.../Services/Admin/Modules/VersionModule.cs | 14 ++
.../Components/Services/Media/ComicService.cs | 3 +-
.../Components/Services/Media/TaskService.cs | 15 ++-
.../Components/Services/Media/VideoService.cs | 2 +-
Abyss/Components/Static/SocketExtensions.cs | 51 ++++++++
Abyss/Model/Admin/Ctl.cs | 19 +++
Abyss/Program.cs | 10 +-
abyssctl/App/App.cs | 49 +++++++
abyssctl/App/Modules/HelloOptions.cs | 20 +++
abyssctl/App/Modules/VersionOptions.cs | 13 ++
abyssctl/Model/Ctl.cs | 19 +++
abyssctl/Program.cs | 13 ++
abyssctl/Static/SocketExtensions.cs | 50 ++++++++
abyssctl/abyssctl.csproj | 24 ++++
26 files changed, 578 insertions(+), 79 deletions(-)
create mode 100644 .idea/.idea.Abyss/.idea/dictionaries/project.xml
create mode 100644 Abyss/Components/Services/Admin/Attributes/Module.cs
create mode 100644 Abyss/Components/Services/Admin/CtlService.cs
create mode 100644 Abyss/Components/Services/Admin/Interfaces/IModule.cs
create mode 100644 Abyss/Components/Services/Admin/Modules/HelloModule.cs
create mode 100644 Abyss/Components/Services/Admin/Modules/VersionModule.cs
create mode 100644 Abyss/Components/Static/SocketExtensions.cs
create mode 100644 Abyss/Model/Admin/Ctl.cs
create mode 100644 abyssctl/App/App.cs
create mode 100644 abyssctl/App/Modules/HelloOptions.cs
create mode 100644 abyssctl/App/Modules/VersionOptions.cs
create mode 100644 abyssctl/Model/Ctl.cs
create mode 100644 abyssctl/Program.cs
create mode 100644 abyssctl/Static/SocketExtensions.cs
create mode 100644 abyssctl/abyssctl.csproj
diff --git a/.gitignore b/.gitignore
index b523ad6..6432d04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,4 +57,6 @@ nunit-*.xml
*.db
appsettings.json
-appsettings.Development.json
\ No newline at end of file
+appsettings.Development.json
+build/
+publish/
diff --git a/.idea/.idea.Abyss/.idea/dictionaries/project.xml b/.idea/.idea.Abyss/.idea/dictionaries/project.xml
new file mode 100644
index 0000000..258a037
--- /dev/null
+++ b/.idea/.idea.Abyss/.idea/dictionaries/project.xml
@@ -0,0 +1,7 @@
+
+
+
+ abyssctl
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Abyss/.idea/workspace.xml b/.idea/.idea.Abyss/.idea/workspace.xml
index fb33dea..21de6d0 100644
--- a/.idea/.idea.Abyss/.idea/workspace.xml
+++ b/.idea/.idea.Abyss/.idea/workspace.xml
@@ -4,15 +4,36 @@
Abyss/Abyss.csproj
Abyss/Abyss.csproj
AbyssCli/AbyssCli.csproj
+ abyssctl/abyssctl.csproj
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
@@ -34,6 +55,7 @@
+
@@ -45,6 +67,11 @@
+
+
+
+
+
@@ -55,10 +82,12 @@
+
+
@@ -71,7 +100,10 @@
-
+
+
+
+
@@ -85,51 +117,44 @@
- {
- "keyToString": {
- ".NET Launch Settings Profile.Abyss: http.executor": "Run",
- ".NET Launch Settings Profile.Abyss: https.executor": "Debug",
- ".NET Project.AbyssCli.executor": "Run",
- "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "ModuleVcsDetector.initialDetectionPerformed": "true",
- "Publish to folder.Publish Abyss to folder x86.executor": "Run",
- "Publish to folder.Publish Abyss to folder.executor": "Run",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
- "RunOnceActivity.git.unshallow": "true",
- "XThreadsFramesViewSplitterKey": "0.55813956",
- "git-widget-placeholder": "main",
- "last_opened_file_path": "/home/acite/AciteProjects/Abyss/README.md",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
- "vue.rearranger.settings.migration": "true"
+
-
-
-
-
-
-
-
-
-
-
-
+}]]>
+
+
+
-
-
+
+
-
+
@@ -139,12 +164,12 @@
-
+
-
+
@@ -167,9 +192,8 @@
-
+
-
@@ -254,6 +278,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Abyss.sln b/Abyss.sln
index 08c5c17..ce4ab0d 100644
--- a/Abyss.sln
+++ b/Abyss.sln
@@ -2,6 +2,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abyss", "Abyss\Abyss.csproj", "{3337C1CD-2419-4922-BC92-AF1A825DDF23}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "abyssctl", "abyssctl\abyssctl.csproj", "{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -12,5 +14,9 @@ Global
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Abyss.sln.DotSettings.user b/Abyss.sln.DotSettings.user
index 6bd98a5..726d3e2 100644
--- a/Abyss.sln.DotSettings.user
+++ b/Abyss.sln.DotSettings.user
@@ -7,6 +7,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
diff --git a/Abyss/Abyss.csproj b/Abyss/Abyss.csproj
index ec64339..a5b2311 100644
--- a/Abyss/Abyss.csproj
+++ b/Abyss/Abyss.csproj
@@ -6,6 +6,10 @@
enable
+
+ ../build/
+
+
diff --git a/Abyss/Components/Controllers/Security/UserController.cs b/Abyss/Components/Controllers/Security/UserController.cs
index c5664e1..ce725bb 100644
--- a/Abyss/Components/Controllers/Security/UserController.cs
+++ b/Abyss/Components/Controllers/Security/UserController.cs
@@ -14,7 +14,7 @@ namespace Abyss.Components.Controllers.Security;
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("Fixed")]
-public class UserController(UserService userService, ILogger logger) : BaseController
+public class UserController(UserService userService) : BaseController
{
[HttpGet("{user}")]
public async Task Challenge(string user)
diff --git a/Abyss/Components/Controllers/Task/TaskController.cs b/Abyss/Components/Controllers/Task/TaskController.cs
index 00adfaa..addc1df 100644
--- a/Abyss/Components/Controllers/Task/TaskController.cs
+++ b/Abyss/Components/Controllers/Task/TaskController.cs
@@ -35,27 +35,26 @@ public class TaskController(ConfigureService config, TaskService taskService) :
return Ok(JsonConvert.SerializeObject(r, Formatting.Indented));
}
- [HttpGet("{id}")]
- public async Task GetTask(string id)
- {
- throw new NotImplementedException();
- }
-
- [HttpPatch("{id}")]
- public async Task PutChip(string id)
- {
- throw new NotImplementedException();
- }
-
- [HttpPost("{id}")]
- public async Task VerifyChip(string id)
- {
- throw new NotImplementedException();
- }
-
- [HttpDelete("{id}")]
- public async Task DeleteTask(string id)
- {
- throw new NotImplementedException();
- }
+ // [HttpGet("{id}")]
+ // public async Task GetTask(string id)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //
+ // [HttpPatch("{id}")]
+ // public async Task PutChip(string id)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //
+ // [HttpPost("{id}")]
+ // public async Task VerifyChip(string id)
+ // {
+ // throw new NotImplementedException();
+ // }
+ // [HttpDelete("{id}")]
+ // public async Task DeleteTask(string id)
+ // {
+ // throw new NotImplementedException();
+ // }
}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Admin/Attributes/Module.cs b/Abyss/Components/Services/Admin/Attributes/Module.cs
new file mode 100644
index 0000000..c3e1a4a
--- /dev/null
+++ b/Abyss/Components/Services/Admin/Attributes/Module.cs
@@ -0,0 +1,29 @@
+using System.Reflection;
+using Abyss.Components.Services.Admin.Interfaces;
+
+namespace Abyss.Components.Services.Admin.Attributes;
+
+[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+public class Module(int head) : Attribute
+{
+ public int Head { get; } = head;
+
+ public static Type[] Modules
+ {
+ get
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ Type attributeType = typeof(Module);
+ const string targetNamespace = "Abyss.Components.Services.Admin.Modules";
+
+ var moduleTypes = assembly.GetTypes()
+ .Where(t => t is { IsClass: true, IsAbstract: false, IsInterface: false })
+ .Where(t => t.Namespace == targetNamespace)
+ .Where(t => typeof(IModule).IsAssignableFrom(t))
+ .Where(t => t.IsDefined(attributeType, inherit: false))
+ .ToArray();
+
+ return moduleTypes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Admin/CtlService.cs b/Abyss/Components/Services/Admin/CtlService.cs
new file mode 100644
index 0000000..0073d0e
--- /dev/null
+++ b/Abyss/Components/Services/Admin/CtlService.cs
@@ -0,0 +1,110 @@
+using System.Net.Sockets;
+using System.Text;
+
+using Abyss.Components.Static;
+using Abyss.Model.Admin;
+using Newtonsoft.Json;
+
+using System.Reflection;
+using Abyss.Components.Services.Admin.Interfaces;
+using Module = Abyss.Components.Services.Admin.Attributes.Module;
+
+namespace Abyss.Components.Services.Admin;
+
+public class CtlService(ILogger logger, IServiceProvider serviceProvider) : IHostedService
+{
+ private readonly string _socketPath = "ctl.sock";
+
+ private Task? _executingTask;
+ private CancellationTokenSource? _cts;
+ private Dictionary _handlers = new();
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ var t = Module.Modules;
+ foreach (var module in t)
+ {
+ var attr = module.GetCustomAttribute();
+ if (attr != null)
+ {
+ _handlers[attr.Head] = module;
+ }
+ }
+
+ _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ _executingTask = ExecuteAsync(_cts.Token);
+ return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ if (_executingTask == null)
+ return;
+
+ try
+ {
+ _cts?.CancelAsync();
+ }
+ finally
+ {
+ await Task.WhenAny(_executingTask,
+ Task.Delay(Timeout.Infinite, cancellationToken));
+ }
+ }
+
+ private async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ if (File.Exists(_socketPath))
+ {
+ File.Delete(_socketPath);
+ }
+
+ var endPoint = new UnixDomainSocketEndPoint(_socketPath);
+
+ using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
+ socket.Bind(endPoint);
+ socket.Listen(5);
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ var clientSocket = await socket.AcceptAsync(stoppingToken);
+ _ = HandleClientAsync(clientSocket, stoppingToken);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ }
+ }
+
+ private async Task HandleClientAsync(Socket clientSocket, CancellationToken stoppingToken)
+ {
+ async Task _400()
+ {
+ await clientSocket.WriteBase64Async(Ctl.MakeBase64(400, ["Bad Request"]), stoppingToken);
+ }
+
+ try
+ {
+ var s = Encoding.UTF8.GetString(
+ Convert.FromBase64String(await clientSocket.ReadBase64Async(stoppingToken)));
+ var json = JsonConvert.DeserializeObject(s);
+
+ if (json == null || !_handlers.TryGetValue(json.Head, out var handler))
+ {
+ await _400();
+ return;
+ }
+
+ var module = (serviceProvider.GetRequiredService(handler) as IModule)!;
+ var r = await module.ExecuteAsync(json, stoppingToken);
+ await clientSocket.WriteBase64Async(Ctl.MakeBase64(r.Head, r.Params), stoppingToken);
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, "Error while handling client connection");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Admin/Interfaces/IModule.cs b/Abyss/Components/Services/Admin/Interfaces/IModule.cs
new file mode 100644
index 0000000..a599879
--- /dev/null
+++ b/Abyss/Components/Services/Admin/Interfaces/IModule.cs
@@ -0,0 +1,8 @@
+using Abyss.Model.Admin;
+
+namespace Abyss.Components.Services.Admin.Interfaces;
+
+public interface IModule
+{
+ public Task ExecuteAsync(Ctl request, CancellationToken ct);
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Admin/Modules/HelloModule.cs b/Abyss/Components/Services/Admin/Modules/HelloModule.cs
new file mode 100644
index 0000000..599aa15
--- /dev/null
+++ b/Abyss/Components/Services/Admin/Modules/HelloModule.cs
@@ -0,0 +1,18 @@
+using Abyss.Components.Services.Admin.Attributes;
+using Abyss.Components.Services.Admin.Interfaces;
+using Abyss.Model.Admin;
+
+namespace Abyss.Components.Services.Admin.Modules;
+
+[Module(100)]
+public class HelloModule: IModule
+{
+ public async Task ExecuteAsync(Ctl request, CancellationToken ct)
+ {
+ return await Task.FromResult(new Ctl
+ {
+ Head = 200,
+ Params = ["Hi"],
+ });
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Admin/Modules/VersionModule.cs b/Abyss/Components/Services/Admin/Modules/VersionModule.cs
new file mode 100644
index 0000000..58646ab
--- /dev/null
+++ b/Abyss/Components/Services/Admin/Modules/VersionModule.cs
@@ -0,0 +1,14 @@
+using Abyss.Components.Services.Admin.Attributes;
+using Abyss.Components.Services.Admin.Interfaces;
+using Abyss.Model.Admin;
+
+namespace Abyss.Components.Services.Admin.Modules;
+
+[Module(101)]
+public class VersionModule: IModule
+{
+ public async Task ExecuteAsync(Ctl request, CancellationToken ct)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Components/Services/Media/ComicService.cs b/Abyss/Components/Services/Media/ComicService.cs
index 07fe4f6..6972cb8 100644
--- a/Abyss/Components/Services/Media/ComicService.cs
+++ b/Abyss/Components/Services/Media/ComicService.cs
@@ -3,11 +3,10 @@ using Abyss.Components.Static;
using Abyss.Model.Media;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
-using Task = System.Threading.Tasks.Task;
namespace Abyss.Components.Services.Media;
-public class ComicService(ILogger logger, ResourceService rs, ConfigureService config)
+public class ComicService(ResourceService rs, ConfigureService config)
{
public readonly string ImageFolder = Path.Combine(config.MediaRoot, "Images");
diff --git a/Abyss/Components/Services/Media/TaskService.cs b/Abyss/Components/Services/Media/TaskService.cs
index 130835e..1a3e4a2 100644
--- a/Abyss/Components/Services/Media/TaskService.cs
+++ b/Abyss/Components/Services/Media/TaskService.cs
@@ -10,7 +10,7 @@ namespace Abyss.Components.Services.Media;
-public class TaskService(ILogger logger, ConfigureService config, ResourceService rs, UserService user)
+public class TaskService(ConfigureService config, ResourceService rs, UserService user)
{
public readonly string TaskFolder = Path.Combine(config.MediaRoot, "Tasks");
public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");
@@ -26,7 +26,7 @@ public class TaskService(ILogger logger, ConfigureService config, R
foreach (var i in r ?? [])
{
var p = Helpers.SafePathCombine(TaskFolder, [i, "task.json"]);
- var c = JsonConvert.DeserializeObject(await System.IO.File.ReadAllTextAsync(p ?? ""));
+ var c = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(p ?? ""));
if(c?.Owner == u) s.Add(i);
}
@@ -50,7 +50,8 @@ public class TaskService(ILogger logger, ConfigureService config, R
switch ((TaskType)creation.Type)
{
case TaskType.Image:
- return await CreateImageTask(token, ip, creation);
+ throw new NotImplementedException();
+ // return await CreateImageTask(token, ip, creation);
case TaskType.Video:
return await CreateVideoTask(token, ip, creation);
default:
@@ -105,10 +106,10 @@ public class TaskService(ILogger logger, ConfigureService config, R
return r;
}
- private async Task CreateImageTask(string token, string ip, TaskCreation creation)
- {
- throw new NotImplementedException();
- }
+ // private async Task CreateImageTask(string token, string ip, TaskCreation creation)
+ // {
+ // throw new NotImplementedException();
+ // }
public static uint GenerateUniqueId(string parentDirectory)
{
diff --git a/Abyss/Components/Services/Media/VideoService.cs b/Abyss/Components/Services/Media/VideoService.cs
index a34efa6..5f3036d 100644
--- a/Abyss/Components/Services/Media/VideoService.cs
+++ b/Abyss/Components/Services/Media/VideoService.cs
@@ -7,7 +7,7 @@ using Newtonsoft.Json;
namespace Abyss.Components.Services.Media;
-public class VideoService(ILogger logger, ResourceService rs, ConfigureService config)
+public class VideoService(ResourceService rs, ConfigureService config)
{
public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");
diff --git a/Abyss/Components/Static/SocketExtensions.cs b/Abyss/Components/Static/SocketExtensions.cs
new file mode 100644
index 0000000..1f0989b
--- /dev/null
+++ b/Abyss/Components/Static/SocketExtensions.cs
@@ -0,0 +1,51 @@
+
+using System.Net.Sockets;
+using System.Text;
+
+namespace Abyss.Components.Static;
+
+public static class SocketExtensions
+{
+ public static async Task ReadBase64Async(this Socket socket, CancellationToken cancellationToken = default)
+ {
+ var buffer = new byte[4096];
+ var sb = new StringBuilder();
+
+ while (true)
+ {
+ int bytesRead = await socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ if (bytesRead == 0)
+ throw new SocketException((int)SocketError.ConnectionReset);
+
+ string chunk = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+ sb.Append(chunk);
+
+ int newlineIndex = sb.ToString().IndexOf('\n');
+ if (newlineIndex >= 0)
+ {
+ string base64 = sb.ToString(0, newlineIndex).Trim();
+ sb.Remove(0, newlineIndex + 1);
+ return base64;
+ }
+ }
+ }
+
+ public static async Task WriteBase64Async(this Socket socket, string base64, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrWhiteSpace(base64))
+ throw new ArgumentException("Base64 string cannot be null or empty.", nameof(base64));
+
+ string message = base64 + "\n";
+ byte[] data = Encoding.UTF8.GetBytes(message);
+
+ int totalSent = 0;
+ while (totalSent < data.Length)
+ {
+ int sent = await socket.SendAsync(data.AsMemory(totalSent), SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ if (sent == 0)
+ throw new SocketException((int)SocketError.ConnectionReset);
+
+ totalSent += sent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Model/Admin/Ctl.cs b/Abyss/Model/Admin/Ctl.cs
new file mode 100644
index 0000000..5897380
--- /dev/null
+++ b/Abyss/Model/Admin/Ctl.cs
@@ -0,0 +1,19 @@
+using System.Text;
+using Newtonsoft.Json;
+
+namespace Abyss.Model.Admin;
+
+public class Ctl
+{
+ [JsonProperty("head")]
+ public int Head { get; set; }
+
+ [JsonProperty("params")] public string[] Params { get; set; } = [];
+
+ public static string MakeBase64(int head, string[] param)
+ {
+ return Convert.ToBase64String(
+ Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Ctl
+ { Head = head, Params = param })));
+ }
+}
\ No newline at end of file
diff --git a/Abyss/Program.cs b/Abyss/Program.cs
index ffb83ef..579abb5 100644
--- a/Abyss/Program.cs
+++ b/Abyss/Program.cs
@@ -2,7 +2,9 @@
using System.Threading.RateLimiting;
using Abyss.Components.Controllers.Middleware;
using Abyss.Components.Controllers.Task;
-
+using Abyss.Components.Services.Admin;
+using Abyss.Components.Services.Admin.Attributes;
+using Abyss.Components.Services.Admin.Modules;
using Abyss.Components.Services.Media;
using Abyss.Components.Services.Misc;
using Abyss.Components.Services.Security;
@@ -30,6 +32,12 @@ public class Program
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddHostedService();
+ builder.Services.AddHostedService();
+
+ foreach (var t in Module.Modules)
+ {
+ builder.Services.AddTransient(t);
+ }
builder.Services.AddRateLimiter(options =>
{
diff --git a/abyssctl/App/App.cs b/abyssctl/App/App.cs
new file mode 100644
index 0000000..70b05e0
--- /dev/null
+++ b/abyssctl/App/App.cs
@@ -0,0 +1,49 @@
+
+using System.Net.Sockets;
+using System.Text;
+using abyssctl.App.Modules;
+using abyssctl.Model;
+using abyssctl.Static;
+using CommandLine;
+using Newtonsoft.Json;
+
+namespace abyssctl.App;
+
+public class App
+{
+ private static readonly string SocketPath = "ctl.sock";
+
+ public static async Task CtlWriteRead(Ctl ctl)
+ {
+ var endPoint = new UnixDomainSocketEndPoint(SocketPath);
+ using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
+ try
+ {
+ await socket.ConnectAsync(endPoint);
+ await socket.WriteBase64Async(Ctl.MakeBase64(ctl.Head, ctl.Params));
+ var s = Encoding.UTF8.GetString(
+ Convert.FromBase64String(await socket.ReadBase64Async()));
+ return JsonConvert.DeserializeObject(s)!;
+ }
+ catch (Exception e)
+ {
+ return new Ctl
+ {
+ Head = 500,
+ Params = [e.Message]
+ };
+ }
+ }
+
+ public async Task RunAsync(string[] args)
+ {
+ return await Task.Run(() =>
+ {
+ return Parser.Default.ParseArguments(args)
+ .MapResult(
+ (HelloOptions opt) => HelloOptions.Run(opt),
+ (VersionOptions opt) => VersionOptions.Run(opt),
+ _ => 1);
+ });
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/App/Modules/HelloOptions.cs b/abyssctl/App/Modules/HelloOptions.cs
new file mode 100644
index 0000000..610a37a
--- /dev/null
+++ b/abyssctl/App/Modules/HelloOptions.cs
@@ -0,0 +1,20 @@
+using abyssctl.Model;
+using CommandLine;
+
+namespace abyssctl.App.Modules;
+
+[Verb("hello", HelpText = "Say hello to abyss server")]
+public class HelloOptions
+{
+ public static int Run(HelloOptions opts)
+ {
+ var r = App.CtlWriteRead(new Ctl
+ {
+ Head = 100,
+ Params = []
+ }).GetAwaiter().GetResult();
+ Console.WriteLine($"Response Code: {r.Head}");
+ Console.WriteLine($"Params: {string.Join(",", r.Params)}");
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/App/Modules/VersionOptions.cs b/abyssctl/App/Modules/VersionOptions.cs
new file mode 100644
index 0000000..ea2e9a6
--- /dev/null
+++ b/abyssctl/App/Modules/VersionOptions.cs
@@ -0,0 +1,13 @@
+using CommandLine;
+
+namespace abyssctl.App.Modules;
+
+[Verb("ver", HelpText = "Get server version")]
+public class VersionOptions
+{
+ public static int Run(VersionOptions opts)
+ {
+ Console.WriteLine("Version");
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/Model/Ctl.cs b/abyssctl/Model/Ctl.cs
new file mode 100644
index 0000000..625906a
--- /dev/null
+++ b/abyssctl/Model/Ctl.cs
@@ -0,0 +1,19 @@
+using System.Text;
+using Newtonsoft.Json;
+
+namespace abyssctl.Model;
+
+public class Ctl
+{
+ [JsonProperty("head")] public int Head { get; set; }
+
+ [JsonProperty("params")] public string[] Params { get; set; } = [];
+
+ public static string MakeBase64(int head, string[] param)
+ {
+ return Convert.ToBase64String(
+ Encoding.UTF8.GetBytes(
+ JsonConvert.SerializeObject(new Ctl
+ { Head = head, Params = param })));
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/Program.cs b/abyssctl/Program.cs
new file mode 100644
index 0000000..55e0c10
--- /dev/null
+++ b/abyssctl/Program.cs
@@ -0,0 +1,13 @@
+
+
+namespace abyssctl;
+
+
+static class Program
+{
+ static async Task Main(string[] args)
+ {
+ var app = new App.App();
+ return await app.RunAsync(args);
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/Static/SocketExtensions.cs b/abyssctl/Static/SocketExtensions.cs
new file mode 100644
index 0000000..0ea202e
--- /dev/null
+++ b/abyssctl/Static/SocketExtensions.cs
@@ -0,0 +1,50 @@
+using System.Net.Sockets;
+using System.Text;
+
+namespace abyssctl.Static;
+
+public static class SocketExtensions
+{
+ public static async Task ReadBase64Async(this Socket socket, CancellationToken cancellationToken = default)
+ {
+ var buffer = new byte[4096];
+ var sb = new StringBuilder();
+
+ while (true)
+ {
+ int bytesRead = await socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ if (bytesRead == 0)
+ throw new SocketException((int)SocketError.ConnectionReset);
+
+ string chunk = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+ sb.Append(chunk);
+
+ int newlineIndex = sb.ToString().IndexOf('\n');
+ if (newlineIndex >= 0)
+ {
+ string base64 = sb.ToString(0, newlineIndex).Trim();
+ sb.Remove(0, newlineIndex + 1);
+ return base64;
+ }
+ }
+ }
+
+ public static async Task WriteBase64Async(this Socket socket, string base64, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrWhiteSpace(base64))
+ throw new ArgumentException("Base64 string cannot be null or empty.", nameof(base64));
+
+ string message = base64 + "\n";
+ byte[] data = Encoding.UTF8.GetBytes(message);
+
+ int totalSent = 0;
+ while (totalSent < data.Length)
+ {
+ int sent = await socket.SendAsync(data.AsMemory(totalSent), SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ if (sent == 0)
+ throw new SocketException((int)SocketError.ConnectionReset);
+
+ totalSent += sent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/abyssctl/abyssctl.csproj b/abyssctl/abyssctl.csproj
new file mode 100644
index 0000000..a31e8f8
--- /dev/null
+++ b/abyssctl/abyssctl.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net9.0
+ 13
+ enable
+ enable
+ false
+ true
+
+
+
+ ../build/
+
+
+
+
+
+
+
+
+
+