diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..449e4079 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.vs/ +.vscode/ +_ReSharper.*/ \ No newline at end of file diff --git a/CompatBot/CompatBot.csproj b/CompatBot/CompatBot.csproj index 94c15ba0..3afdd19b 100644 --- a/CompatBot/CompatBot.csproj +++ b/CompatBot/CompatBot.csproj @@ -8,6 +8,7 @@ c2e6548b-b215-4a18-a010-958ef294b310 latest 1701;1702;VSTHRD200 + Linux @@ -37,6 +38,7 @@ + diff --git a/CompatBot/Config.cs b/CompatBot/Config.cs index 2b3b398d..6ace1493 100644 --- a/CompatBot/Config.cs +++ b/CompatBot/Config.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Threading; +using CompatBot.Utils; using DSharpPlus.Entities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.UserSecrets; @@ -54,13 +55,16 @@ namespace CompatBot public static int MinimumPiracyTriggerLength => config.GetValue(nameof(MinimumPiracyTriggerLength), 4); public static string Token => config.GetValue(nameof(Token), ""); - public static string LogPath => config.GetValue(nameof(LogPath), "logs/bot.log"); // paths are relative to the assembly, so this will put it in the project's root + public static string LogPath => config.GetValue(nameof(LogPath), "../../../logs/"); // paths are relative to the assembly, so this will put it in the project's root public static string IrdCachePath => config.GetValue(nameof(IrdCachePath), "./ird/"); public static string GoogleApiConfigPath { get { + if (SandboxDetector.Detect() == "Docker") + return "/bot-config/credentials.json"; + if (Assembly.GetEntryAssembly().GetCustomAttribute() is UserSecretsIdAttribute attribute) { var path = Path.GetDirectoryName(PathHelper.GetSecretsPathFromSecretsId(attribute.UserSecretsId)); @@ -68,6 +72,7 @@ namespace CompatBot if (File.Exists(path)) return path; } + return "Properties/credentials.json"; } } @@ -154,7 +159,7 @@ namespace CompatBot config = new ConfigurationBuilder() .AddUserSecrets(Assembly.GetExecutingAssembly()) // lower priority - //.AddEnvironmentVariables() + .AddEnvironmentVariables() .AddInMemoryCollection(inMemorySettings) // higher priority .Build(); Log = GetLog(); @@ -172,7 +177,7 @@ namespace CompatBot { var config = new NLog.Config.LoggingConfiguration(); var fileTarget = new FileTarget("logfile") { - FileName = Path.Combine("../../../", LogPath), + FileName = Path.Combine(LogPath, "bot.log"), ArchiveEvery = FileArchivePeriod.Day, ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, KeepFileOpen = true, diff --git a/CompatBot/Database/DbImporter.cs b/CompatBot/Database/DbImporter.cs index 4bb2b74b..3b772292 100644 --- a/CompatBot/Database/DbImporter.cs +++ b/CompatBot/Database/DbImporter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using CompatBot.Database.Migrations; +using CompatBot.Utils; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -110,6 +111,9 @@ namespace CompatBot.Database internal static string GetDbPath(string dbName, Environment.SpecialFolder desiredFolder) { + if (SandboxDetector.Detect() == "Docker") + return Path.Combine("/bot-db/", dbName); + var settingsFolder = Path.Combine(Environment.GetFolderPath(desiredFolder), "compat-bot"); try { diff --git a/CompatBot/Program.cs b/CompatBot/Program.cs index b9b5cf23..c19d32a2 100644 --- a/CompatBot/Program.cs +++ b/CompatBot/Program.cs @@ -1,5 +1,6 @@ ο»Ώusing System; using System.IO; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using CompatBot.Commands; @@ -8,10 +9,12 @@ using CompatBot.Database; using CompatBot.Database.Providers; using CompatBot.EventHandlers; using CompatBot.ThumbScrapper; +using CompatBot.Utils; using DSharpPlus; using DSharpPlus.CommandsNext; using DSharpPlus.Entities; using DSharpPlus.Interactivity; +using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.DependencyInjection; namespace CompatBot @@ -24,6 +27,17 @@ namespace CompatBot internal static async Task Main(string[] args) { + if (args.Length > 0 && args[0] == "--dry-run") + { + Console.WriteLine("Confinement: " + SandboxDetector.Detect()); + Console.WriteLine("Database path: " + Path.GetDirectoryName(Path.GetFullPath(DbImporter.GetDbPath("fake.db", Environment.SpecialFolder.ApplicationData)))); + if (Assembly.GetEntryAssembly().GetCustomAttribute() is UserSecretsIdAttribute attribute) + { + Console.WriteLine("Bot config path: " + Path.GetDirectoryName(Path.GetFullPath(Config.GoogleApiConfigPath))); + } + return; + } + var singleInstanceCheckThread = new Thread(() => { using (var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot")) diff --git a/CompatBot/Properties/launchSettings.json b/CompatBot/Properties/launchSettings.json index 978fca35..09e012f9 100644 --- a/CompatBot/Properties/launchSettings.json +++ b/CompatBot/Properties/launchSettings.json @@ -2,6 +2,9 @@ "profiles": { "default": { "commandName": "Project" + }, + "Docker": { + "commandName": "Docker" } } } \ No newline at end of file diff --git a/CompatBot/Utils/Extensions/StringUtils.cs b/CompatBot/Utils/Extensions/StringUtils.cs index a9660c3c..bbaae199 100644 --- a/CompatBot/Utils/Extensions/StringUtils.cs +++ b/CompatBot/Utils/Extensions/StringUtils.cs @@ -218,7 +218,7 @@ namespace CompatBot.Utils return result.ToString(0, result.Length-1); } - public static string GetMoons(decimal? stars) + public static string GetMoons(decimal? stars, bool haveFun = true) { if (!stars.HasValue) return null; @@ -232,7 +232,7 @@ namespace CompatBot.Utils if (halfStar == 4) { - if (new Random().Next(100) == 69) + if (haveFun && new Random().Next(100) == 69) result += "🌝"; else result += "πŸŒ•"; @@ -246,7 +246,7 @@ namespace CompatBot.Utils for (var i = 0; i < noStars; i++) { - if (i == 0 && halfStar == 0 && new Random().Next(100) == 69) + if (haveFun && i == 0 && halfStar == 0 && new Random().Next(100) == 69) result += "🌚"; else result += "πŸŒ‘"; diff --git a/CompatBot/Utils/SandboxDetector.cs b/CompatBot/Utils/SandboxDetector.cs index 2d3e33a7..4eb04efb 100644 --- a/CompatBot/Utils/SandboxDetector.cs +++ b/CompatBot/Utils/SandboxDetector.cs @@ -12,6 +12,9 @@ namespace CompatBot.Utils if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FLATPAK_SYSTEM_DIR"))) return "Flatpak"; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RUNNING_IN_DOCKER"))) + return "Docker"; + return null; } } diff --git a/CompatBot/Utils/TimeParser.cs b/CompatBot/Utils/TimeParser.cs index b3fb43f8..42e7c93d 100644 --- a/CompatBot/Utils/TimeParser.cs +++ b/CompatBot/Utils/TimeParser.cs @@ -82,5 +82,7 @@ namespace CompatBot.Utils return date.ToUniversalTime(); return date.AsUtc(); } + + public static List GetSupportedTimeZoneAbbreviations() => TimeZoneMap.Keys.ToList(); } } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b4c4e704 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:latest AS base +COPY packages /root/.nuget/packages/ +WORKDIR /src +COPY . . +RUN rm -rf ./packages +RUN git status +RUN dotnet build "CompatBot/CompatBot.csproj" -c Release +ENV RUNNING_IN_DOCKER true +WORKDIR /src/CompatBot +RUN dotnet run -c Release --dry-run +ENTRYPOINT ["dotnet", "run", "-c Release", "CompatBot.csproj"] \ No newline at end of file diff --git a/README.md b/README.md index c0f0b3e1..c6cd2632 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ How to Build How to Run in Production ------------------------ + +### Running from source * Change configuration if needed (probably just token): * use `$ dotnet user-secrets set Token ` * for available configuration variables, see [Config.cs](CompatBot/Config.cs#L31) @@ -54,6 +56,11 @@ How to Run in Production * `$ cd CompatBot` * `$ dotnet run -c Release` +### Running with Docker +* Official image is hosted on [Docker Hub](https://hub.docker.com/r/13xforever/rpcs3-discord-bot). +* You should pull images tagged with `release-latest` (same thing as `latest`) +* Please take a look at the [docker-compose.yml](docker-compose.yml) for required configuration (bot token and mounting points for persistent data). + External resources that need manual updates ------------------------------------------- * [Unicode confusables](http://www.unicode.org/Public/security/latest/confusables.txt) gzipped, for Homoglyph checks diff --git a/Tests/IrdTests.cs b/Tests/IrdTests.cs index 9220ef92..2b6e78a4 100644 --- a/Tests/IrdTests.cs +++ b/Tests/IrdTests.cs @@ -4,7 +4,7 @@ using NUnit.Framework; namespace Tests { - [TestFixture] + [TestFixture, Explicit("Requires files to run")] public class IrdTests { [Test] diff --git a/Tests/StarsFormatTest.cs b/Tests/StarsFormatTest.cs index 259afc05..294b572c 100644 --- a/Tests/StarsFormatTest.cs +++ b/Tests/StarsFormatTest.cs @@ -23,7 +23,7 @@ namespace Tests [TestCase(1.0, "πŸŒ•πŸŒ‘πŸŒ‘πŸŒ‘πŸŒ‘")] public void FormatTest(decimal score, string expectedValue) { - Assert.That(StringUtils.GetMoons(score), Is.EqualTo(expectedValue), "Failed for " + score); + Assert.That(StringUtils.GetMoons(score, false), Is.EqualTo(expectedValue), "Failed for " + score); } } } diff --git a/Tests/TimeParserTests.cs b/Tests/TimeParserTests.cs index 0a9cfe34..30406922 100644 --- a/Tests/TimeParserTests.cs +++ b/Tests/TimeParserTests.cs @@ -8,12 +8,12 @@ namespace Tests public class TimeParserTests { [TestCase("2019-8-19 6:00 PT", "2019-08-19T13:00Z")] - [TestCase("2019-8-19 17:00 bst", "2019-08-19T16:00Z")] + [TestCase("2019-8-19 17:00 cest", "2019-08-19T15:00Z")] [TestCase("2019-9-1 22:00 jst", "2019-09-01T13:00Z")] public void TimeZoneConverterTest(string input, string utcInput) { var utc = DateTime.Parse(utcInput).Normalize(); - Assert.That(TimeParser.TryParse(input, out var result), Is.True); + Assert.That(TimeParser.TryParse(input, out var result), Is.True, $"{input} failed to parse\nSupported time zones: {string.Join(", ", TimeParser.GetSupportedTimeZoneAbbreviations())}"); Assert.That(result, Is.EqualTo(utc)); Assert.That(result.Kind, Is.EqualTo(DateTimeKind.Utc)); } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 985be7aa..e48f065a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,20 +1,75 @@ -# ASP.NET Core -# Build and test ASP.NET Core projects targeting .NET Core. -# Add steps that run tests, create a NuGet package, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core +jobs: +- job: BuildAndTest + displayName: 'Run tests' + pool: + vmImage: 'ubuntu-latest' + steps: + - script: dotnet restore --ignore-failed-sources + displayName: 'dotnet restore (first try)' -pool: - vmImage: 'Ubuntu 16.04' + - script: dotnet restore --ignore-failed-sources + displayName: 'dotnet restore (second try)' -variables: - buildConfiguration: 'Release' + - script: dotnet restore + displayName: 'dotnet restore (last try)' -steps: -- script: dotnet restore - displayName: 'dotnet restore' + - script: dotnet build --configuration Debug + displayName: 'dotnet build Debug' -- script: dotnet build --configuration Debug - displayName: 'dotnet build Debug' + - task: DotNetCoreCLI@2 + displayName: 'dotnet test' + inputs: + command: 'test' + projects: Tests/Tests.csproj -- script: dotnet build --configuration Release - displayName: 'dotnet build Release' +- job: DockerImage + displayName: 'Build Docker image' + condition: and(and(succeeded(), eq(variables['ReleaseBranch'], variables['Build.SourceBranch'])), ne(format('{0}', variables['DockerConnection']), '')) + dependsOn: BuildAndTest + pool: + vmImage: 'ubuntu-latest' + steps: + - script: git checkout -f $(Build.SourceBranchName) + displayName: 'create local tracking branch' + + - script: git clean -dfx + displayName: 'clean build artifacts' + + - script: dotnet restore --ignore-failed-sources + displayName: 'dotnet restore (first try)' + + - script: dotnet restore --ignore-failed-sources + displayName: 'dotnet restore (second try)' + + - script: dotnet restore + displayName: 'dotnet restore (last try)' + + - script: dotnet build --configuration Release + displayName: 'dotnet build Release' + + - script: mkdir packages && cp -a /home/vsts/.nuget/packages ./packages/ + displayName: 'copy nuget package cache for docker' + + - task: Docker@2 + displayName: 'building release docker image' + condition: and(succeeded(), eq(variables['ReleaseKind'], 'release')) + inputs: + containerRegistry: $(DockerConnection) + repository: $(DockerRegistry) + command: 'buildAndPush' + Dockerfile: 'Dockerfile' + tags: | + $(Build.BuildId) + release-latest + latest + + - task: Docker@2 + displayName: 'building test docker image' + condition: and(succeeded(), not(eq(variables['ReleaseKind'], 'release'))) + inputs: + containerRegistry: $(DockerConnection) + repository: $(DockerRegistry) + command: 'buildAndPush' + Dockerfile: 'Dockerfile' + tags: test-latest + diff --git a/discord-bot-net.sln b/discord-bot-net.sln index fb16dcae..45ae4504 100644 --- a/discord-bot-net.sln +++ b/discord-bot-net.sln @@ -1,7 +1,7 @@ ο»Ώ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2035 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28917.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompatBot", "CompatBot\CompatBot.csproj", "{6D9CA448-60C1-4D66-91D6-EC6C586508E6}" EndProject @@ -17,9 +17,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Clients", "Clients", "{E7FE0ADD-CBA6-4321-8A1C-0A3B5C3F54C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GithubClient", "Clients\GithubClient\GithubClient.csproj", "{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GithubClient", "Clients\GithubClient\GithubClient.csproj", "{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppveyorClient", "Clients\AppveyorClient\AppveyorClient.csproj", "{595ED201-1456-49F9-AD60-54B08499A5C1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppveyorClient", "Clients\AppveyorClient\AppveyorClient.csproj", "{595ED201-1456-49F9-AD60-54B08499A5C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AD87F38F-BFCE-4EA6-A430-20C497552FD7}" + ProjectSection(SolutionItems) = preProject + azure-pipelines.yml = azure-pipelines.yml + docker-compose.example.yml = docker-compose.example.yml + Dockerfile = Dockerfile + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 00000000..b92e1565 --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,13 @@ +version: "3" +services: + bot: + image: rpcs3/discord-bot:latest + volumes: + - /home/MY_USER_NAME/.local/share/compat-bot:/bot-db + - /home/MY_USER_NAME/.microsoft/usersecrets/c2e6548b-b215-4a18-a010-958ef294b310:/bot-config + - /var/logs/compat-bot:/src/CompatBot/logs + - /ver/ird:/var/ird + environment: + Token: MY_BOT_TOKEN + LogPath: /var/logs/compat-bot + IrdCachePath: /var/ird