Skip to content

Commit

Permalink
docs and process fix
Browse files Browse the repository at this point in the history
  • Loading branch information
cruikshj committed May 7, 2024
1 parent 6f15a1d commit 9cf2688
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 62 deletions.
61 changes: 23 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,54 +39,23 @@ This bot application is distributed as a self-contained executable. The executa

### Configuration

Configuration of the bot application can be done in a variety of ways. The application uses `Microsoft.Extensions.Configuration` with the [WebApplication.CreateBuilder defaults](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0#default-application-configuration-sources), plus `appsettings.yml` support, `SERVERMANAGER_` environment variable prefix support and support for reading all `.json` and `.yaml` files from a `Config` directory. You can learn how to use standard configuration providers [here](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers). You may even mix and match these forms of configuration to fit your needs. When using file based configuration, you will need to mount the configuration files into your container. The examples below will use the `appsettings.yaml` form of configuration.
Configuration of the bot application can be done in a variety of ways. The application uses `Microsoft.Extensions.Configuration` with the [WebApplication.CreateBuilder defaults](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0#default-application-configuration-sources), plus `appsettings.yml` support, `SERVERMANAGER_` environment variable prefix support and support for reading all `.json` and `.yaml` files from a `Config` directory. You can learn how to use standard configuration providers [here](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers). You may even mix and match these forms of configuration to fit your needs. When using file based configuration, you will need to mount the configuration files into your container.

#### Basic
#### Examples

```yaml
BotToken: "<your token from Discord"
Servers:
"minecraft-1":
Game: Minecraft (Bedrock)
Icon: https://cdn2.steamgriddb.com/icon_thumb/4a5b76e7170df685ed8b75c7dacce268.png
Fields:
Address: example.com:12345
Mode: Survival
```
#### Kubernetes Hosted
```yaml
BotToken: "<your token from Discord"
ServerInfoProviders:
- Type: KubernetesConfigMap
ServerHostAdapters:
Kubernetes:
Type: Kubernetes
```
#### Handling Large File Downloads
<a name="handlinglargefiledownloads"></a>
Each example will show how to host the bot application itself as well as integrating with the hosting platform to control dedicated servers. The bot application does not have to be hosted the same way as the dedicated servers. Feel free to host the bot application however you see fit.

Files 25MB or less will be sent via Discord interactions. Files greater than this limit cannot be sent via Discord. The `LargeFileDownloadHandler` is intended to provide an alternative download link in these cases. The current options are `Disabled` and `BuiltIn`.

##### BuiltIn Handler

```yaml
HostUri: https://smdb.example.com/
LargeFileDownloadHandler: BuiltIn
DownloadLinkExpiration: "24:00:00"
```

The `BuiltInLargeFileDownloadHandler` will create a temporary download link using the `HostUri` and a guid and make it available to download a file for the `DownloadLinkExpiration`. If this feature is used, HTTP traffic to the app will need to be exposed to your end users in some way. This is the only endpoint exposed by the bot. If this feature is not used, no exposue is necessary.
- [Process](examples/process.md)
- [Docker Compose](examples/docker-compose.md)
- [Kubernetes](examples/kubernetes.md)

#### All Settings

| Section | Description | Default |
|---|---|---|
| BotToken | (Required) The Discord Bot token. | |
| GuildIds | An array of Discord guild (server) IDs. This can be used for testing or security purposes to limit which Discord servers the bot application will communicate with. | |
| HostUri | The bot application host URI. Only used if `LargeFileDownloadHandler.BuiltIn` is used. | https://localhost:8080 |
| HostUri | The bot application host URI. Only used if `LargeFileDownloadHandler.BuiltIn` is used. | https://localhost:5000 |
| EnableFileDownloadHandler | This is a global setting for whether to enable the file downloads feature. Configured servers must still opt-in by providing a `FilesPath` value. | true |
| LargeFileDownloadHandler | Enable file downloads larger than 25MB. See [Handling Large File Downloads](#handlinglargefiledownloads). | Disabled |
| ServersCacheExpiration | The server info cache expiration. | 5 minutes |
Expand All @@ -111,6 +80,7 @@ The `BuiltInLargeFileDownloadHandler` will create a temporary download link usin
| -Key- | (Required) The section key or name is used to lookup the adapter matching the `HostAdapter` value on server info. | |
| Type | (Required) The type of the adapter. Can be `Process`, `DockerCompose`, or `Kubernetes`. | |
| DockerProcessFilePath (DockerCompose) | The file name of the docker executable. | docker |
| DockerHost (DockerCompose) | Daemon socket to connect to. | |
| KubeConfigPath (Kubernetes) | The path to a Kube Config file to use to connect to Kubernetes. If not provided, `InCluster` configuration will be used. | |

##### Servers
Expand All @@ -133,6 +103,21 @@ The `BuiltInLargeFileDownloadHandler` will create a temporary download link usin
| HostProperties.Namespace (Kubernetes) | The workload namespace. | |
| HostProperties.Name (Kubernetes) | The workload name. | |

#### Handling Large File Downloads
<a name="handlinglargefiledownloads"></a>

Files 25MB or less will be sent via Discord interactions. Files greater than this limit cannot be sent via Discord. The `LargeFileDownloadHandler` is intended to provide an alternative download link in these cases. The current options are `Disabled` and `BuiltIn`.

##### BuiltIn Handler

```yaml
HostUri: https://smdb.example.com/
LargeFileDownloadHandler: BuiltIn
DownloadLinkExpiration: "24:00:00"
```
The `BuiltInLargeFileDownloadHandler` will create a temporary download link using the `HostUri` and a guid and make it available to download a file for the `DownloadLinkExpiration`. If this feature is used, HTTP traffic to the app will need to be exposed to your end users in some way. This is the only endpoint exposed by the bot. If this feature is not used, no exposue is necessary.

## Contribution

This repository is open to contribution. I tried to decouple many of the opinionated features (such as Kubernetes integrations) and allow for extensibility.
Expand Down
Empty file added examples/docker-compose.md
Empty file.
Empty file added examples/kubernetes.md
Empty file.
79 changes: 79 additions & 0 deletions examples/process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Process examples

The following examples show how to setup the bot application using the executable process. As discussed in the README, configuration can be done in a variety of ways. These examples will use `appsettings.yaml` for configuration, but feel free to use environment variables, command line arguments, other file types or the config directory.

## Bot application

This example shows how to run the bot application executable.

_folder structure (windows)_
```
C:\some\folder
ServerManager.DiscordBot.exe
appsettings.yaml
```

_folder structure (linux)_
```
/some/folder
ServerManager.DiscordBot
appsettings.yaml
```

_appsettings.yaml_
```yaml
BotToken: "<your token from Discord>"
Servers:
"minecraft-1":
Game: Minecraft (Bedrock)
Icon: https://cdn2.steamgriddb.com/icon_thumb/4a5b76e7170df685ed8b75c7dacce268.png
Fields:
Address: example.com:12345
Mode: Survival
"minecraft-2":
Game: Minecraft (Java)
Icon: https://cdn2.steamgriddb.com/icon_thumb/4a5b76e7170df685ed8b75c7dacce268.png
Fields:
Address: example.com:54321
Mode: Creative
Version: 1.12.2
Readme: |
# Minecraft
This is a Minecraft server.
```
Simply start the executable. It connect to the Discord gateway and remain running until stopped. Consider using operating system features to run as a service.
## Process integration
This example expands on the above example to show how the bot can managed a dedicated server process.
_folder structure (windows)_
```
C:\some\folder
ServerManager.DiscordBot.exe
appsettings.yaml

C:\other\folder
\minecraft-1
bedrock_server.exe
...
```

_appsettings.yaml_
```yaml
BotToken: "<your token from Discord>"
ServerHostAdapters:
Process:
Type: Process
Servers:
"minecraft-1":
Game: Minecraft (Bedrock)
Icon: https://cdn2.steamgriddb.com/icon_thumb/4a5b76e7170df685ed8b75c7dacce268.png
Fields:
Address: example.com:12345
Mode: Survival
HostAdapterName: Process
HostProperties:
FileName: "C:\\other\\folder\\minecraft-1\\bedrock_server.exe"
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,9 @@ public class ProcessServerHostAdapter(

public override Task<ServerStatus> GetServerStatusAsync(CancellationToken cancellationToken = default)
{
var processName = Path.GetFileName(Context.Properties.FileName);
using var process = GetProcess();

var processes = Process.GetProcessesByName(processName);

var status = processes.Length > 0
? ServerStatus.Running
: ServerStatus.Stopped;

processes.DisposeAll();

return Task.FromResult(status);
return Task.FromResult(process is not null ? ServerStatus.Running : ServerStatus.Stopped);
}

public override async Task StartServerAsync(CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -53,25 +45,19 @@ public override async Task StopServerAsync(CancellationToken cancellationToken =
throw new InvalidOperationException("Server is already stopped.");
}

var processName = Path.GetFileName(Context.Properties.FileName);

var processes = Process.GetProcessesByName(processName);
using var process = GetProcess();

foreach (var process in processes)
if (process is not null)
{
process.Kill();
}

processes.DisposeAll();
await process.WaitForExitAsync(cancellationToken);
}
}

public override async Task<IDictionary<string, Stream>> GetServerLogsAsync(CancellationToken cancellationToken = default)
{
var processName = Path.GetFileName(Context.Properties.FileName);

var processes = Process.GetProcessesByName(processName);

var process = processes.FirstOrDefault();
{
using var process = GetProcess();

var logs = new Dictionary<string, Stream>();

Expand Down Expand Up @@ -100,8 +86,37 @@ public override async Task<IDictionary<string, Stream>> GetServerLogsAsync(Cance
}
}

processes.DisposeAll();

return logs;
}

private Process? GetProcess()
{
var fullFileName = Context.Properties.FileName;
if (!string.IsNullOrEmpty(Context.Properties.WorkingDirectory))
{
fullFileName = Path.Combine(Context.Properties.WorkingDirectory, fullFileName);
}

var fileName = Path.GetFileName(fullFileName);

var allProcesses = Process.GetProcessesByName(fileName);

var processes = allProcesses.Where(p => p.MainModule?.FileName == fullFileName);

if (processes.Count() > 1)
{
processes.DisposeAll();

throw new InvalidOperationException("Multiple processes with the same file name are running.");
}

var process = processes.FirstOrDefault();

if (process is not null)
{
allProcesses.Except([process]).DisposeAll();
}

return process;
}
}

0 comments on commit 9cf2688

Please sign in to comment.