rework server responsibilities (#18)
* rework server responsibilities add remote configuration * start metrics only when compiled as not debug * add some more logging to discord bot * fixes of some casts * make metrics port configurable, minor fixes * add docker bullshit * md formatting * adjustments to docker stuff * fix docker json files, fix some stuff in discord bot, add /useradd for Discord bot * adjust docker configs and fix sharded.bat * fixes for logs, cache file provider repeat trying to open filestream Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
284
.dockerignore
Normal file
284
.dockerignore
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
# Created by https://www.gitignore.io/api/csharp
|
||||||
|
|
||||||
|
### Csharp ###
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# DNX
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
Properties/launchSettings.json
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignoreable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush
|
||||||
|
.cr/
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/
|
||||||
|
tools/Cake.CoreCLR
|
||||||
|
.vscode
|
||||||
|
tools
|
||||||
|
.dotnet
|
||||||
|
Dockerfile
|
||||||
|
|
||||||
|
# .env file contains default environment variables for docker
|
||||||
|
.env
|
||||||
|
.git/
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -348,3 +348,6 @@ MigrationBackup/
|
|||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
.ionide/
|
.ionide/
|
||||||
|
|
||||||
|
# docker run data
|
||||||
|
Docker/run/data/
|
||||||
38
Docker/Readme.md
Normal file
38
Docker/Readme.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Mare Synchronos Docker Setup
|
||||||
|
This is primarily aimed at developers who want to spin up their own local server for development purposes without having to spin up a VM.
|
||||||
|
Obligatory requires Docker to be installed on the machine.
|
||||||
|
|
||||||
|
There are two directories: `build` and `run`
|
||||||
|
|
||||||
|
## 1. build images
|
||||||
|
There is two ways to build the necessary docker images which are differentiated by the folders `-local` and `-git`
|
||||||
|
- -local will run the image build against the current locally present sources
|
||||||
|
- -git will run the image build against the latest git main commit
|
||||||
|
It is possible to build all required images at once by running `docker-build.bat/sh` (Server, Servies, StaticFilesServer) or all 3 separately with `docker-build-<whatever>.bat/sh`
|
||||||
|
|
||||||
|
## 2. Configure ports + token
|
||||||
|
You should set up 2 environment variables that hold server specific configuration and open up ports.
|
||||||
|
The default ports used through the provided configuration are `6000` for the main server and `6200` for the files downloads.
|
||||||
|
Both ports should be open to your computer through your router if you wish to test this with clients.
|
||||||
|
|
||||||
|
Furthermore there are two environment variables `DEV_MARE_CDNURL` and `DEV_MARE_DISCORDTOKEN` which you are required to set.
|
||||||
|
`DEV_MARE_CDNURL` should point to `http://<yourip or dyndns>:6200/cache/` and `DEV_MARE_DISCORDTOKEN` is an oauth token from a bot you need to create through the Discord bot portal.
|
||||||
|
It is enough to set them as User variables. The compose files refer to those environment variables to overwrite configuration settings for the Server and Services to set those respective values.
|
||||||
|
It is also possible to set those values in the configuration.json files themselves.
|
||||||
|
Without a valid Discord bot you will not be able to register accounts without fumbling around in the PostgreSQL database.
|
||||||
|
|
||||||
|
## 3. Run Mare Server
|
||||||
|
The run folder contains two major Mare configurations which is `standalone` and `sharded`.
|
||||||
|
Both configurations default to port `6000` for the main server connection and `6200` for the files downloads. No HTTPS.
|
||||||
|
All `appsettings.json` configurations provided are extensive at the point of writing, note the differences between the shard configurations and the main servers respectively.
|
||||||
|
They can be used as examples if you want to spin up your own servers otherwise.
|
||||||
|
|
||||||
|
The scripts to start the respective services are divided by name, the `daemon-start/stop` files use `compose up -d` to run it in the background and to be able to stop the containers as well.
|
||||||
|
The respective docker-compose files lie in the `compose` folder. I would not recommend editing them unless you know what you are doing.
|
||||||
|
All data (postgresql and files uploads) will be thrown into the `data` folder after startup.
|
||||||
|
All logs from the mare services will be thrown into `logs`, divided by shard, where applicable.
|
||||||
|
|
||||||
|
The `standalone` configuration features PostgeSQL, Mare Server, Mare StaticFilesServer and Mare Services.
|
||||||
|
The `sharded` configuration features PostgreSQL, Redis, HAProxy, Mare Server Main, 2 Mare Server Shards, Mare Services, Mare StaticFilesServer Main and 2 Mare StaticFilesServer Shards.
|
||||||
|
Haproxy is set up that it takes the same ports as the `standalone` configuration and distributes the connections between the shards.
|
||||||
|
In theory it should be possible to switch between the `standalone` and `sharded` configuration by shutting down one composition container and starting up the other. They share the same Database.
|
||||||
31
Docker/build/Dockerfile-MareSynchronosServer
Normal file
31
Docker/build/Dockerfile-MareSynchronosServer
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
COPY MareAPI /server/MareAPI
|
||||||
|
COPY MareSynchronosServer/MareSynchronosShared /server/MareSynchronosServer/MareSynchronosShared
|
||||||
|
COPY MareSynchronosServer/MareSynchronosServer /server/MareSynchronosServer/MareSynchronosServer
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosServer/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/build \
|
||||||
|
MareSynchronosServer.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /build /opt/MareSynchronosServer
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosServer
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosServer
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosServer"]
|
||||||
29
Docker/build/Dockerfile-MareSynchronosServer-git
Normal file
29
Docker/build/Dockerfile-MareSynchronosServer-git
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
RUN git clone --recurse-submodules https://github.com/Penumbra-Sync/server
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosServer/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/MareSynchronosServer \
|
||||||
|
MareSynchronosServer.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /MareSynchronosServer /opt/MareSynchronosServer
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosServer
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosServer
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosServer"]
|
||||||
31
Docker/build/Dockerfile-MareSynchronosServices
Normal file
31
Docker/build/Dockerfile-MareSynchronosServices
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
COPY MareAPI /server/MareAPI
|
||||||
|
COPY MareSynchronosServer/MareSynchronosShared /server/MareSynchronosServer/MareSynchronosShared
|
||||||
|
COPY MareSynchronosServer/MareSynchronosServices /server/MareSynchronosServer/MareSynchronosServices
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosServices/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/build \
|
||||||
|
MareSynchronosServices.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /build /opt/MareSynchronosServices
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosServices
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosServices
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosServices"]
|
||||||
29
Docker/build/Dockerfile-MareSynchronosServices-git
Normal file
29
Docker/build/Dockerfile-MareSynchronosServices-git
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
RUN git clone --recurse-submodules https://github.com/Penumbra-Sync/server
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosServices/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/MareSynchronosServices \
|
||||||
|
MareSynchronosServices.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /MareSynchronosServices /opt/MareSynchronosServices
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosServices
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosServices
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosServices"]
|
||||||
31
Docker/build/Dockerfile-MareSynchronosStaticFilesServer
Normal file
31
Docker/build/Dockerfile-MareSynchronosStaticFilesServer
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
COPY MareAPI /server/MareAPI
|
||||||
|
COPY MareSynchronosServer/MareSynchronosShared /server/MareSynchronosServer/MareSynchronosShared
|
||||||
|
COPY MareSynchronosServer/MareSynchronosStaticFilesServer /server/MareSynchronosServer/MareSynchronosStaticFilesServer
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosStaticFilesServer/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/build \
|
||||||
|
MareSynchronosStaticFilesServer.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /build /opt/MareSynchronosStaticFilesServer
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosStaticFilesServer
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosStaticFilesServer
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosStaticFilesServer"]
|
||||||
29
Docker/build/Dockerfile-MareSynchronosStaticFilesServer-git
Normal file
29
Docker/build/Dockerfile-MareSynchronosStaticFilesServer-git
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 as BUILD
|
||||||
|
|
||||||
|
RUN git clone --recurse-submodules https://github.com/Penumbra-Sync/server
|
||||||
|
|
||||||
|
WORKDIR /server/MareSynchronosServer/MareSynchronosStaticFilesServer/
|
||||||
|
|
||||||
|
RUN dotnet publish \
|
||||||
|
--configuration=Release \
|
||||||
|
--os=linux \
|
||||||
|
--output=/MareSynchronosStaticFilesServer \
|
||||||
|
MareSynchronosStaticFilesServer.csproj
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
|
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--group \
|
||||||
|
--no-create-home \
|
||||||
|
--quiet \
|
||||||
|
--system \
|
||||||
|
mare
|
||||||
|
|
||||||
|
COPY --from=BUILD /MareSynchronosStaticFilesServer /opt/MareSynchronosStaticFilesServer
|
||||||
|
RUN chown -R mare:mare /opt/MareSynchronosStaticFilesServer
|
||||||
|
|
||||||
|
USER mare:mare
|
||||||
|
WORKDIR /opt/MareSynchronosStaticFilesServer
|
||||||
|
|
||||||
|
CMD ["./MareSynchronosStaticFilesServer"]
|
||||||
2
Docker/build/linux-git/docker-build-server.sh
Normal file
2
Docker/build/linux-git/docker-build-server.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker build -t darkarchon/mare-synchronos-server:latest . -f ../Dockerfile-MareSynchronosServer-git --no-cache --pull --force-rm
|
||||||
2
Docker/build/linux-git/docker-build-services.sh
Normal file
2
Docker/build/linux-git/docker-build-services.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker build -t darkarchon/mare-synchronos-services:latest . -f ../Dockerfile-MareSynchronosServices-git --no-cache --pull --force-rm
|
||||||
2
Docker/build/linux-git/docker-build-staticfilesserver.sh
Normal file
2
Docker/build/linux-git/docker-build-staticfilesserver.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker build -t darkarchon/mare-synchronos-staticfilesserver:latest . -f ../Dockerfile-MareSynchronosStaticFilesServer-git --no-cache --pull --force-rm
|
||||||
4
Docker/build/linux-git/docker-build.sh
Normal file
4
Docker/build/linux-git/docker-build.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
./docker-build-server.sh
|
||||||
|
./docker-build-services.sh
|
||||||
|
./docker-build-staticfilesserver.sh
|
||||||
4
Docker/build/linux-local/docker-build-server.sh
Normal file
4
Docker/build/linux-local/docker-build-server.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
cd ../../../
|
||||||
|
docker build -t darkarchon/mare-synchronos-server:latest . -f ../Dockerfile-MareSynchronosServer --no-cache --pull --force-rm
|
||||||
|
cd Docker/build/linux-local
|
||||||
4
Docker/build/linux-local/docker-build-services.sh
Normal file
4
Docker/build/linux-local/docker-build-services.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
cd ../../../
|
||||||
|
docker build -t darkarchon/mare-synchronos-services:latest . -f ../Dockerfile-MareSynchronosServices --no-cache --pull --force-rm
|
||||||
|
cd Docker/build/linux-local
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
cd ../../../
|
||||||
|
docker build -t darkarchon/mare-synchronos-staticfilesserver:latest . -f ../Dockerfile-MareSynchronosStaticFilesServer --no-cache --pull --force-rm
|
||||||
|
cd Docker/build/linux-local
|
||||||
4
Docker/build/linux-local/docker-build.sh
Normal file
4
Docker/build/linux-local/docker-build.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
./docker-build-server.sh
|
||||||
|
./docker-build-services.sh
|
||||||
|
./docker-build-staticfilesserver.sh
|
||||||
2
Docker/build/windows-git/docker-build-server.bat
Normal file
2
Docker/build/windows-git/docker-build-server.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker build -t darkarchon/mare-synchronos-server:latest . -f ..\Dockerfile-MareSynchronosServer-git --no-cache --pull --force-rm
|
||||||
3
Docker/build/windows-git/docker-build-services.bat
Normal file
3
Docker/build/windows-git/docker-build-services.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
docker build -t darkarchon/mare-synchronos-services:latest . -f ..\Dockerfile-MareSynchronosServices-git --no-cache --pull --force-rm
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
docker build -t darkarchon/mare-synchronos-staticfilesserver:latest . -f ..\Dockerfile-MareSynchronosStaticFilesServer-git --no-cache --pull --force-rm
|
||||||
5
Docker/build/windows-git/docker-build.bat
Normal file
5
Docker/build/windows-git/docker-build.bat
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
call docker-build-server.bat
|
||||||
|
call docker-build-services.bat
|
||||||
|
call docker-build-staticfilesserver.bat
|
||||||
4
Docker/build/windows-local/docker-build-server.bat
Normal file
4
Docker/build/windows-local/docker-build-server.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
cd ..\..\..\
|
||||||
|
docker build -t darkarchon/mare-synchronos-server:latest . -f Docker\build\Dockerfile-MareSynchronosServer --no-cache --pull --force-rm
|
||||||
|
cd Docker\build\windows-local
|
||||||
4
Docker/build/windows-local/docker-build-services.bat
Normal file
4
Docker/build/windows-local/docker-build-services.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
cd ..\..\..\
|
||||||
|
docker build -t darkarchon/mare-synchronos-services:latest . -f Docker\build\Dockerfile-MareSynchronosServices --no-cache --pull --force-rm
|
||||||
|
cd Docker\build\windows-local
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
cd ..\..\..\
|
||||||
|
docker build -t darkarchon/mare-synchronos-staticfilesserver:latest . -f Docker\build\Dockerfile-MareSynchronosStaticFilesServer --no-cache --pull --force-rm
|
||||||
|
cd Docker\build\windows-local
|
||||||
5
Docker/build/windows-local/docker-build.bat
Normal file
5
Docker/build/windows-local/docker-build.bat
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
call docker-build-server.bat
|
||||||
|
call docker-build-services.bat
|
||||||
|
call docker-build-staticfilesserver.bat
|
||||||
114
Docker/run/compose/mare-sharded.yml
Normal file
114
Docker/run/compose/mare-sharded.yml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: mare
|
||||||
|
POSTGRES_USER: mare
|
||||||
|
POSTGRES_PASSWORD: secretdevpassword
|
||||||
|
volumes:
|
||||||
|
- ../data/postgresql/:/var/lib/postgresql/data
|
||||||
|
- postgres_socket:/var/run/postgresql:rw
|
||||||
|
|
||||||
|
haproxy:
|
||||||
|
image: haproxy:latest
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 6000:6000/tcp
|
||||||
|
- 6200:6200/tcp
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/haproxy-shards.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
command: [sh, -c, "rm -f /data/dump.rdb && redis-server --save \"\" --appendonly no --requirepass secretredispassword"]
|
||||||
|
volumes:
|
||||||
|
- cache:/data
|
||||||
|
|
||||||
|
mare-server:
|
||||||
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
|
restart: on-failure
|
||||||
|
environment:
|
||||||
|
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}"
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/server-shard-main.json:/opt/MareSynchronosServer/appsettings.json
|
||||||
|
- ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
|
||||||
|
mare-shard-1:
|
||||||
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/server-shard-1.json:/opt/MareSynchronosServer/appsettings.json
|
||||||
|
- ../log/server-shard-1/:/opt/MareSynchronosServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
mare-shard-2:
|
||||||
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/server-shard-2.json:/opt/MareSynchronosServer/appsettings.json
|
||||||
|
- ../log/server-shard-2/:/opt/MareSynchronosServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
mare-services:
|
||||||
|
image: darkarchon/mare-synchronos-services:latest
|
||||||
|
restart: on-failure
|
||||||
|
environment:
|
||||||
|
MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}"
|
||||||
|
volumes:
|
||||||
|
- ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json
|
||||||
|
- ../log/services-standalone/:/opt/MareSynchronosServices/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
mare-files:
|
||||||
|
image: darkarchon/mare-synchronos-staticfilesserver:latest
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/files-shard-main.json:/opt/MareSynchronosStaticFilesServer/appsettings.json
|
||||||
|
- ../log/files-standalone/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
- ../data/files-shard-main/:/marecache/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
mare-files-shard-1:
|
||||||
|
image: darkarchon/mare-synchronos-staticfilesserver:latest
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/files-shard-1.json:/opt/MareSynchronosStaticFilesServer/appsettings.json
|
||||||
|
- ../log/files-shard-1/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
- ../data/files-shard-1/:/marecache/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-files"
|
||||||
|
|
||||||
|
mare-files-shard-2:
|
||||||
|
image: darkarchon/mare-synchronos-staticfilesserver:latest
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/sharded/files-shard-2.json:/opt/MareSynchronosStaticFilesServer/appsettings.json
|
||||||
|
- ../log/files-shard-2/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
- ../data/files-shard-2/:/marecache/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-files"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cache:
|
||||||
|
driver: local
|
||||||
|
postgres_socket:
|
||||||
55
Docker/run/compose/mare-standalone.yml
Normal file
55
Docker/run/compose/mare-standalone.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: mare
|
||||||
|
POSTGRES_USER: mare
|
||||||
|
POSTGRES_PASSWORD: secretdevpassword
|
||||||
|
volumes:
|
||||||
|
- ../data/postgresql/:/var/lib/postgresql/data
|
||||||
|
- postgres_socket:/var/run/postgresql:rw
|
||||||
|
|
||||||
|
mare-server:
|
||||||
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
|
restart: on-failure
|
||||||
|
ports:
|
||||||
|
- 6000:6000/tcp
|
||||||
|
environment:
|
||||||
|
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}"
|
||||||
|
volumes:
|
||||||
|
- ../config/standalone/server-standalone.json:/opt/MareSynchronosServer/appsettings.json
|
||||||
|
- ../log/server-standalone/:/opt/MareSynchronosServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
|
||||||
|
mare-services:
|
||||||
|
image: darkarchon/mare-synchronos-services:latest
|
||||||
|
restart: on-failure
|
||||||
|
environment:
|
||||||
|
MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}"
|
||||||
|
volumes:
|
||||||
|
- ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json
|
||||||
|
- ../log/services-standalone/:/opt/MareSynchronosServices/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
mare-files:
|
||||||
|
image: darkarchon/mare-synchronos-staticfilesserver:latest
|
||||||
|
ports:
|
||||||
|
- 6200:6200/tcp
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ../config/standalone/files-standalone.json:/opt/MareSynchronosStaticFilesServer/appsettings.json
|
||||||
|
- ../log/files-standalone/:/opt/MareSynchronosStaticFilesServer/logs/:rw
|
||||||
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
|
- ../data/files-standalone/:/marecache/:rw
|
||||||
|
depends_on:
|
||||||
|
- "postgres"
|
||||||
|
- "mare-server"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_socket:
|
||||||
49
Docker/run/config/sharded/files-shard-1.json
Normal file
49
Docker/run/config/sharded/files-shard-1.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosStaticFilesServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Files Shard 1",
|
||||||
|
"MetricsPort": 6250,
|
||||||
|
"FileServerGrpcAddress": "http://mare-files:6205",
|
||||||
|
"ForcedDeletionOfFilesAfterHours": 2,
|
||||||
|
"CacheSizeHardLimitInGiB": 5,
|
||||||
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
|
"CacheDirectory": "/marecache/",
|
||||||
|
"RemoteCacheSourceUri": "http://mare-files:6200/cache/",
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
49
Docker/run/config/sharded/files-shard-2.json
Normal file
49
Docker/run/config/sharded/files-shard-2.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosStaticFilesServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Files Shard 2",
|
||||||
|
"MetricsPort": 6250,
|
||||||
|
"FileServerGrpcAddress": "http://mare-files:6205",
|
||||||
|
"ForcedDeletionOfFilesAfterHours": 2,
|
||||||
|
"CacheSizeHardLimitInGiB": 5,
|
||||||
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
|
"CacheDirectory": "/marecache/",
|
||||||
|
"RemoteCacheSourceUri": "http://mare-files:6200/cache/",
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
53
Docker/run/config/sharded/files-shard-main.json
Normal file
53
Docker/run/config/sharded/files-shard-main.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosStaticFilesServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Files",
|
||||||
|
"MetricsPort": 6250,
|
||||||
|
"FileServerGrpcAddress": "",
|
||||||
|
"ForcedDeletionOfFilesAfterHours": -1,
|
||||||
|
"CacheSizeHardLimitInGiB": -1,
|
||||||
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
|
"CacheDirectory": "/marecache/",
|
||||||
|
"RemoteCacheSourceUri": "",
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6200"
|
||||||
|
},
|
||||||
|
"Grpc": {
|
||||||
|
"Protocols": "Http2",
|
||||||
|
"Url": "http://+:6205"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
39
Docker/run/config/sharded/haproxy-shards.cfg
Normal file
39
Docker/run/config/sharded/haproxy-shards.cfg
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
global
|
||||||
|
log /dev/log local0
|
||||||
|
log /dev/log local1 notice
|
||||||
|
daemon
|
||||||
|
|
||||||
|
ca-base /etc/ssl/certs
|
||||||
|
crt-base /etc/ssl/private
|
||||||
|
|
||||||
|
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode http
|
||||||
|
option httplog
|
||||||
|
option dontlognull
|
||||||
|
timeout connect 5000
|
||||||
|
timeout client 50000
|
||||||
|
timeout server 50000
|
||||||
|
|
||||||
|
frontend mare
|
||||||
|
bind :6000
|
||||||
|
default_backend mare-servers
|
||||||
|
|
||||||
|
frontend mare-files
|
||||||
|
bind :6200
|
||||||
|
default_backend mare-files
|
||||||
|
|
||||||
|
backend mare-servers
|
||||||
|
balance leastconn
|
||||||
|
cookie SERVER insert indirect nocache
|
||||||
|
server mare1 mare-shard-1:6000 cookie mare1
|
||||||
|
server mare2 mare-shard-2:6000 cookie mare2
|
||||||
|
|
||||||
|
backend mare-files
|
||||||
|
balance roundrobin
|
||||||
|
server files1 mare-files-shard-1:6200
|
||||||
|
server files2 mare-files-shard-2:6200
|
||||||
44
Docker/run/config/sharded/server-shard-1.json
Normal file
44
Docker/run/config/sharded/server-shard-1.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Shard 1",
|
||||||
|
"MetricsPort": 6050,
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
44
Docker/run/config/sharded/server-shard-2.json
Normal file
44
Docker/run/config/sharded/server-shard-2.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Shard 2",
|
||||||
|
"MetricsPort": 6050,
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
60
Docker/run/config/sharded/server-shard-main.json
Normal file
60
Docker/run/config/sharded/server-shard-main.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Main",
|
||||||
|
"MetricsPort": 6050,
|
||||||
|
"MainServerGrpcAddress": "",
|
||||||
|
"FailedAuthForTempBan": 5,
|
||||||
|
"TempBanDurationInMinutes": 5,
|
||||||
|
"WhitelistedIps": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"RedisConnectionString": "redis,password=secretredispassword",
|
||||||
|
"CdnFullUrl": "http://localhost:6200/cache/",
|
||||||
|
"StaticFileServiceAddress": "http://mare-files:6205",
|
||||||
|
"MaxExistingGroupsByUser": 3,
|
||||||
|
"MaxJoinedGroupsByUser": 6,
|
||||||
|
"MaxGroupUserCount": 100,
|
||||||
|
"PurgeUnusedAccounts": false,
|
||||||
|
"PurgeUnusedAccountsPeriodInDays": 14
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6000"
|
||||||
|
},
|
||||||
|
"Grpc": {
|
||||||
|
"Protocols": "Http2",
|
||||||
|
"Url": "http://+:6005"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
53
Docker/run/config/standalone/files-standalone.json
Normal file
53
Docker/run/config/standalone/files-standalone.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosStaticFilesServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Files",
|
||||||
|
"MetricsPort": 6250,
|
||||||
|
"FileServerGrpcAddress": "",
|
||||||
|
"ForcedDeletionOfFilesAfterHours": -1,
|
||||||
|
"CacheSizeHardLimitInGiB": -1,
|
||||||
|
"UnusedFileRetentionPeriodInDays": 14,
|
||||||
|
"CacheDirectory": "/marecache/",
|
||||||
|
"RemoteCacheSourceUri": "",
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6200"
|
||||||
|
},
|
||||||
|
"Grpc": {
|
||||||
|
"Protocols": "Http2",
|
||||||
|
"Url": "http://+:6205"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
60
Docker/run/config/standalone/server-standalone.json
Normal file
60
Docker/run/config/standalone/server-standalone.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosServer": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Main",
|
||||||
|
"MetricsPort": 6050,
|
||||||
|
"MainServerGrpcAddress": "",
|
||||||
|
"FailedAuthForTempBan": 5,
|
||||||
|
"TempBanDurationInMinutes": 5,
|
||||||
|
"WhitelistedIps": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"RedisConnectionString": "",
|
||||||
|
"CdnFullUrl": "http://localhost:6200/cache/",
|
||||||
|
"StaticFileServiceAddress": "http://mare-files:6205",
|
||||||
|
"MaxExistingGroupsByUser": 3,
|
||||||
|
"MaxJoinedGroupsByUser": 6,
|
||||||
|
"MaxGroupUserCount": 100,
|
||||||
|
"PurgeUnusedAccounts": false,
|
||||||
|
"PurgeUnusedAccountsPeriodInDays": 14
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Http": {
|
||||||
|
"Url": "http://+:6000"
|
||||||
|
},
|
||||||
|
"Grpc": {
|
||||||
|
"Protocols": "Http2",
|
||||||
|
"Url": "http://+:6005"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
39
Docker/run/config/standalone/services-standalone.json
Normal file
39
Docker/run/config/standalone/services-standalone.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=/var/run/postgresql;Port=5432;Database=mare;Username=mare;Keepalive=15;Minimum Pool Size=10;Maximum Pool Size=50;No Reset On Close=true;Max Auto Prepare=50;Enlist=false"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"MareSynchronosServices": "Information",
|
||||||
|
"MareSynchronosShared": "Information",
|
||||||
|
"System.IO": "Information"
|
||||||
|
},
|
||||||
|
"File": {
|
||||||
|
"BasePath": "logs",
|
||||||
|
"FileAccessMode": "KeepOpenAndAutoFlush",
|
||||||
|
"FileEncodingName": "utf-8",
|
||||||
|
"DateFormat": "yyyMMdd",
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"Files": [
|
||||||
|
{
|
||||||
|
"Path": "<date:yyyy>/<date:MM>/<date:dd>/mare-<date:HH>-<counter:0000>.log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"DbContextPoolSize": 512,
|
||||||
|
"ShardName": "Services",
|
||||||
|
"MetricsPort": 6150,
|
||||||
|
"MainServerGrpcAddress": "http://mare-server:6005",
|
||||||
|
"DiscordBotToken": ""
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
},
|
||||||
|
"IpRateLimiting": {},
|
||||||
|
"IPRateLimitPolicies": {}
|
||||||
|
}
|
||||||
2
Docker/run/linux-sharded-daemon-start.sh
Normal file
2
Docker/run/linux-sharded-daemon-start.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-sharded.yml -p sharded up -d
|
||||||
2
Docker/run/linux-sharded-daemon-stop.sh
Normal file
2
Docker/run/linux-sharded-daemon-stop.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-sharded.yml -p sharded stop
|
||||||
2
Docker/run/linux-sharded.sh
Normal file
2
Docker/run/linux-sharded.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-sharded.yml -p sharded up
|
||||||
2
Docker/run/linux-standalone-daemon-start.sh
Normal file
2
Docker/run/linux-standalone-daemon-start.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-standalone.yml -p standalone up -d
|
||||||
2
Docker/run/linux-standalone-daemon-stop.sh
Normal file
2
Docker/run/linux-standalone-daemon-stop.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-standalone.yml -p standalone stop
|
||||||
2
Docker/run/linux-standalone.sh
Normal file
2
Docker/run/linux-standalone.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
docker compose -f compose/mare-standalone.yml -p standalone up
|
||||||
2
Docker/run/windows-sharded-daemon-start.bat
Normal file
2
Docker/run/windows-sharded-daemon-start.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-sharded.yml -p sharded up -d
|
||||||
2
Docker/run/windows-sharded-daemon-stop.bat
Normal file
2
Docker/run/windows-sharded-daemon-stop.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-sharded.yml -p sharded stop
|
||||||
2
Docker/run/windows-sharded.bat
Normal file
2
Docker/run/windows-sharded.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-sharded.yml -p sharded up
|
||||||
2
Docker/run/windows-standalone-daemon-start.bat
Normal file
2
Docker/run/windows-standalone-daemon-start.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-standalone.yml -p standalone up -d
|
||||||
2
Docker/run/windows-standalone-daemon-stop.bat
Normal file
2
Docker/run/windows-standalone-daemon-stop.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-standalone.yml -p standalone stop
|
||||||
2
Docker/run/windows-standalone.bat
Normal file
2
Docker/run/windows-standalone.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
docker compose -f compose\mare-standalone.yml -p standalone up
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using MareSynchronos.API;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MareSynchronos.API;
|
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
@@ -11,6 +8,7 @@ namespace MareSynchronosServer.Hubs;
|
|||||||
|
|
||||||
public partial class MareHub
|
public partial class MareHub
|
||||||
{
|
{
|
||||||
|
// TODO: remove all of this and migrate it to the discord bot eventually
|
||||||
private List<string> OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList();
|
private List<string> OnlineAdmins => _dbContext.Users.Where(u => (u.IsModerator || u.IsAdmin)).Select(u => u.UID).ToList();
|
||||||
|
|
||||||
[Authorize(Policy = "Admin")]
|
[Authorize(Policy = "Admin")]
|
||||||
@@ -116,11 +114,11 @@ public partial class MareHub
|
|||||||
|
|
||||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
await Clients.Users(OnlineAdmins).Client_AdminUpdateOrAddBannedUser(dto).ConfigureAwait(false);
|
await Clients.Users(OnlineAdmins).Client_AdminUpdateOrAddBannedUser(dto).ConfigureAwait(false);
|
||||||
var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
|
//var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
|
||||||
if (!string.IsNullOrEmpty(bannedUser))
|
//if (!string.IsNullOrEmpty(bannedUser))
|
||||||
{
|
//{
|
||||||
await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
|
// await Clients.User(bannedUser).Client_AdminForcedReconnect().ConfigureAwait(false);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Admin")]
|
[Authorize(Policy = "Admin")]
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Hubs
|
namespace MareSynchronosServer.Hubs
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
using System;
|
using System.Security.Claims;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System;
|
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ using MareSynchronosShared.Utils;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Hubs;
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using System.Text.RegularExpressions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
@@ -72,7 +68,7 @@ public partial class MareHub
|
|||||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||||
|
|
||||||
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(ownIdent).ConfigureAwait(false);
|
var usersToSendOnlineTo = await SendOnlineToAllPairedUsers(ownIdent).ConfigureAwait(false);
|
||||||
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(System.StringComparer.Ordinal).ToList();
|
return usersToSendOnlineTo.Select(e => _clientIdentService.GetCharacterIdentForUid(e)).Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Identified")]
|
[Authorize(Policy = "Identified")]
|
||||||
@@ -152,8 +148,8 @@ public partial class MareHub
|
|||||||
|
|
||||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||||
|
|
||||||
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), System.StringComparer.Ordinal)
|
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, f => _clientIdentService.GetCharacterIdentForUid(f), StringComparer.Ordinal)
|
||||||
.Where(f => visibleCharacterIds.Contains(f.Value, System.StringComparer.Ordinal));
|
.Where(f => visibleCharacterIds.Contains(f.Value, StringComparer.Ordinal));
|
||||||
|
|
||||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||||
|
|
||||||
@@ -172,7 +168,7 @@ public partial class MareHub
|
|||||||
|
|
||||||
// don't allow adding yourself or nothing
|
// don't allow adding yourself or nothing
|
||||||
uid = uid.Trim();
|
uid = uid.Trim();
|
||||||
if (string.Equals(uid, AuthenticatedUserId, System.StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
if (string.Equals(uid, AuthenticatedUserId, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||||
|
|
||||||
// grab other user, check if it exists and if a pair already exists
|
// grab other user, check if it exists and if a pair already exists
|
||||||
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
||||||
@@ -231,7 +227,7 @@ public partial class MareHub
|
|||||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||||
|
|
||||||
// if the other user has paused the main user and there was no previous group connection don't send anything
|
// if the other user has paused the main user and there was no previous group connection don't send anything
|
||||||
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, System.StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
|
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
|
||||||
{
|
{
|
||||||
await Clients.User(user.UID).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
|
await Clients.User(user.UID).Client_UserChangePairedPlayer(otherIdent, true).ConfigureAwait(false);
|
||||||
await Clients.User(otherUser.UID).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
|
await Clients.User(otherUser.UID).Client_UserChangePairedPlayer(userIdent, true).ConfigureAwait(false);
|
||||||
@@ -243,7 +239,7 @@ public partial class MareHub
|
|||||||
{
|
{
|
||||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, isPaused));
|
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid, isPaused));
|
||||||
|
|
||||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||||
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||||
if (pair == null) return;
|
if (pair == null) return;
|
||||||
|
|
||||||
@@ -288,7 +284,7 @@ public partial class MareHub
|
|||||||
{
|
{
|
||||||
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid));
|
_logger.LogCallInfo(MareHubLogger.Args(otherUserUid));
|
||||||
|
|
||||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
if (string.Equals(otherUserUid, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||||
|
|
||||||
// check if client pair even exists
|
// check if client pair even exists
|
||||||
ClientPair callerPair =
|
ClientPair callerPair =
|
||||||
@@ -331,7 +327,7 @@ public partial class MareHub
|
|||||||
if (!callerHadPaused && otherHadPaused) return;
|
if (!callerHadPaused && otherHadPaused) return;
|
||||||
|
|
||||||
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||||
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, System.StringComparison.Ordinal));
|
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, otherUserUid, StringComparison.Ordinal));
|
||||||
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
|
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
|
||||||
|
|
||||||
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing
|
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
using System;
|
using System.Security.Claims;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
|
using MareSynchronosShared;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Hubs;
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
@@ -23,7 +20,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
private readonly FileService.FileServiceClient _fileServiceClient;
|
private readonly FileService.FileServiceClient _fileServiceClient;
|
||||||
private readonly SystemInfoService _systemInfoService;
|
private readonly SystemInfoService _systemInfoService;
|
||||||
private readonly IHttpContextAccessor _contextAccessor;
|
private readonly IHttpContextAccessor _contextAccessor;
|
||||||
private readonly GrpcClientIdentificationService _clientIdentService;
|
private readonly IClientIdentificationService _clientIdentService;
|
||||||
private readonly MareHubLogger _logger;
|
private readonly MareHubLogger _logger;
|
||||||
private readonly MareDbContext _dbContext;
|
private readonly MareDbContext _dbContext;
|
||||||
private readonly Uri _cdnFullUri;
|
private readonly Uri _cdnFullUri;
|
||||||
@@ -33,18 +30,18 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
private readonly int _maxGroupUserCount;
|
private readonly int _maxGroupUserCount;
|
||||||
|
|
||||||
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
|
public MareHub(MareMetrics mareMetrics, FileService.FileServiceClient fileServiceClient,
|
||||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IOptions<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
|
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService,
|
||||||
GrpcClientIdentificationService clientIdentService)
|
IConfigurationService<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor,
|
||||||
|
IClientIdentificationService clientIdentService)
|
||||||
{
|
{
|
||||||
_mareMetrics = mareMetrics;
|
_mareMetrics = mareMetrics;
|
||||||
_fileServiceClient = fileServiceClient;
|
_fileServiceClient = fileServiceClient;
|
||||||
_systemInfoService = systemInfoService;
|
_systemInfoService = systemInfoService;
|
||||||
var config = configuration.Value;
|
_cdnFullUri = configuration.GetValue<Uri>(nameof(ServerConfiguration.CdnFullUrl));
|
||||||
_cdnFullUri = config.CdnFullUrl;
|
_shardName = configuration.GetValue<string>(nameof(ServerConfiguration.ShardName));
|
||||||
_shardName = config.ShardName;
|
_maxExistingGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3);
|
||||||
_maxExistingGroupsByUser = config.MaxExistingGroupsByUser;
|
_maxJoinedGroupsByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxJoinedGroupsByUser), 6);
|
||||||
_maxJoinedGroupsByUser = config.MaxJoinedGroupsByUser;
|
_maxGroupUserCount = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxGroupUserCount), 100);
|
||||||
_maxGroupUserCount = config.MaxGroupUserCount;
|
|
||||||
_contextAccessor = contextAccessor;
|
_contextAccessor = contextAccessor;
|
||||||
_clientIdentService = clientIdentService;
|
_clientIdentService = clientIdentService;
|
||||||
_logger = new MareHubLogger(this, logger);
|
_logger = new MareHubLogger(this, logger);
|
||||||
@@ -109,14 +106,14 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Authenticated")]
|
[Authorize(Policy = "Authenticated")]
|
||||||
public async Task<bool> CheckClientHealth()
|
public Task<bool> CheckClientHealth()
|
||||||
{
|
{
|
||||||
var needsReconnect = !_clientIdentService.IsOnCurrentServer(AuthenticatedUserId);
|
var needsReconnect = !_clientIdentService.IsOnCurrentServer(AuthenticatedUserId);
|
||||||
if (needsReconnect)
|
if (needsReconnect)
|
||||||
{
|
{
|
||||||
_logger.LogCallWarning(MareHubLogger.Args(needsReconnect));
|
_logger.LogCallWarning(MareHubLogger.Args(needsReconnect));
|
||||||
}
|
}
|
||||||
return needsReconnect;
|
return Task.FromResult(needsReconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
using System;
|
using System.Security.Claims;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AspNetCoreRateLimit;
|
using AspNetCoreRateLimit;
|
||||||
using Microsoft.AspNetCore.Http;
|
using MareSynchronosShared;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace MareSynchronosServer.Utils;
|
namespace MareSynchronosServer.Hubs;
|
||||||
public class SignalRLimitFilter : IHubFilter
|
public class SignalRLimitFilter : IHubFilter
|
||||||
{
|
{
|
||||||
private readonly IRateLimitProcessor _processor;
|
private readonly IRateLimitProcessor _processor;
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices.Identity;
|
namespace MareSynchronosServer.Identity;
|
||||||
|
|
||||||
public class IdentityHandler
|
public class IdentityHandler
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, ServerIdentity> cachedIdentities = new();
|
private readonly ConcurrentDictionary<string, ServerIdentity> _cachedIdentities = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<IdentChange>> identChanges = new();
|
private readonly ConcurrentDictionary<string, ConcurrentQueue<IdentChange>> _identChanges = new(StringComparer.Ordinal);
|
||||||
private readonly ILogger<IdentityHandler> _logger;
|
private readonly ILogger<IdentityHandler> _logger;
|
||||||
|
|
||||||
public IdentityHandler(ILogger<IdentityHandler> logger)
|
public IdentityHandler(ILogger<IdentityHandler> logger)
|
||||||
@@ -18,16 +14,9 @@ public class IdentityHandler
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Task<string> GetUidForCharacterIdent(string ident, string serverId)
|
internal Task<ServerIdentity> GetIdentForUid(string uid)
|
||||||
{
|
{
|
||||||
var exists = cachedIdentities.Any(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId);
|
if (!_cachedIdentities.TryGetValue(uid, out ServerIdentity result))
|
||||||
return Task.FromResult(exists ? cachedIdentities.FirstOrDefault(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId).Key : string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Task<ServerIdentity> GetIdentForuid(string uid)
|
|
||||||
{
|
|
||||||
ServerIdentity result;
|
|
||||||
if (!cachedIdentities.TryGetValue(uid, out result))
|
|
||||||
{
|
{
|
||||||
result = new ServerIdentity();
|
result = new ServerIdentity();
|
||||||
}
|
}
|
||||||
@@ -37,40 +26,40 @@ public class IdentityHandler
|
|||||||
|
|
||||||
internal void SetIdent(string uid, string serverId, string ident)
|
internal void SetIdent(string uid, string serverId, string ident)
|
||||||
{
|
{
|
||||||
cachedIdentities[uid] = new ServerIdentity() { ServerId = serverId, CharacterIdent = ident };
|
_cachedIdentities[uid] = new ServerIdentity() { ServerId = serverId, CharacterIdent = ident };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RemoveIdent(string uid, string serverId)
|
internal void RemoveIdent(string uid, string serverId)
|
||||||
{
|
{
|
||||||
if (cachedIdentities.ContainsKey(uid) && cachedIdentities[uid].ServerId == serverId)
|
if (_cachedIdentities.ContainsKey(uid) && string.Equals(_cachedIdentities[uid].ServerId, serverId, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
cachedIdentities.TryRemove(uid, out _);
|
_cachedIdentities.TryRemove(uid, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal int GetOnlineUsers(string serverId)
|
internal int GetOnlineUsers(string serverId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(serverId))
|
if (string.IsNullOrEmpty(serverId))
|
||||||
return cachedIdentities.Count;
|
return _cachedIdentities.Count;
|
||||||
return cachedIdentities.Count(c => c.Value.ServerId == serverId);
|
return _cachedIdentities.Count(c => string.Equals(c.Value.ServerId, serverId, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Dictionary<string, ServerIdentity> GetIdentsForAllExcept(string serverId)
|
internal Dictionary<string, ServerIdentity> GetIdentsForAllExcept(string serverId)
|
||||||
{
|
{
|
||||||
return cachedIdentities.Where(k => k.Value.ServerId != serverId).ToDictionary(k => k.Key, k => k.Value);
|
return _cachedIdentities.Where(k => !string.Equals(k.Value.ServerId, serverId, StringComparison.Ordinal)).ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Dictionary<string, ServerIdentity> GetIdentsForServer(string serverId)
|
internal Dictionary<string, ServerIdentity> GetIdentsForServer(string serverId)
|
||||||
{
|
{
|
||||||
return cachedIdentities.Where(k => k.Value.ServerId == serverId).ToDictionary(k => k.Key, k => k.Value);
|
return _cachedIdentities.Where(k => string.Equals(k.Value.ServerId, serverId, StringComparison.Ordinal)).ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearIdentsForServer(string serverId)
|
internal void ClearIdentsForServer(string serverId)
|
||||||
{
|
{
|
||||||
var serverIdentities = cachedIdentities.Where(i => i.Value.ServerId == serverId);
|
var serverIdentities = _cachedIdentities.Where(i => string.Equals(i.Value.ServerId, serverId, StringComparison.Ordinal));
|
||||||
foreach (var identity in serverIdentities)
|
foreach (var identity in serverIdentities)
|
||||||
{
|
{
|
||||||
cachedIdentities.TryRemove(identity.Key, out _);
|
_cachedIdentities.TryRemove(identity.Key, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,16 +67,16 @@ public class IdentityHandler
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Enqueued " + identchange.UidWithIdent.Uid.Uid + ":" + identchange.IsOnline + " from " + identchange.UidWithIdent.Ident.ServerId);
|
_logger.LogInformation("Enqueued " + identchange.UidWithIdent.Uid.Uid + ":" + identchange.IsOnline + " from " + identchange.UidWithIdent.Ident.ServerId);
|
||||||
|
|
||||||
foreach (var k in identChanges.Keys)
|
foreach (var k in _identChanges.Keys)
|
||||||
{
|
{
|
||||||
if (string.Equals(k, identchange.UidWithIdent.Ident.ServerId, System.StringComparison.Ordinal)) continue;
|
if (string.Equals(k, identchange.UidWithIdent.Ident.ServerId, StringComparison.Ordinal)) continue;
|
||||||
identChanges[k].Enqueue(identchange);
|
_identChanges[k].Enqueue(identchange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool DequeueIdentChange(string server, out IdentChange? cur)
|
internal bool DequeueIdentChange(string server, out IdentChange cur)
|
||||||
{
|
{
|
||||||
if (!(identChanges.ContainsKey(server) && identChanges[server].TryDequeue(out cur)))
|
if (!(_identChanges.ContainsKey(server) && _identChanges[server].TryDequeue(out cur)))
|
||||||
{
|
{
|
||||||
cur = null;
|
cur = null;
|
||||||
return false;
|
return false;
|
||||||
@@ -98,7 +87,7 @@ public class IdentityHandler
|
|||||||
|
|
||||||
internal void RegisterServerForQueue(string serverId)
|
internal void RegisterServerForQueue(string serverId)
|
||||||
{
|
{
|
||||||
identChanges[serverId] = new ConcurrentQueue<IdentChange>();
|
_identChanges[serverId] = new ConcurrentQueue<IdentChange>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal record ServerIdentity
|
internal record ServerIdentity
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<UserSecretsId>aspnet-MareSynchronosServer-BA82A12A-0B30-463C-801D-B7E81318CD50</UserSecretsId>
|
<UserSecretsId>aspnet-MareSynchronosServer-BA82A12A-0B30-463C-801D-B7E81318CD50</UserSecretsId>
|
||||||
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using Microsoft.Extensions.Options;
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
|
||||||
namespace MareSynchronosServer;
|
namespace MareSynchronosServer;
|
||||||
|
|
||||||
@@ -22,9 +16,12 @@ public class Program
|
|||||||
{
|
{
|
||||||
var services = scope.ServiceProvider;
|
var services = scope.ServiceProvider;
|
||||||
using var context = services.GetRequiredService<MareDbContext>();
|
using var context = services.GetRequiredService<MareDbContext>();
|
||||||
|
var options = services.GetRequiredService<IConfigurationService<ServerConfiguration>>();
|
||||||
|
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||||
|
logger.LogInformation("Loaded MareSynchronos Server Configuration (IsMain: {isMain})", options.IsMain);
|
||||||
|
logger.LogInformation(options.ToString());
|
||||||
|
|
||||||
var secondaryServer = Environment.GetEnvironmentVariable("SECONDARY_SERVER");
|
if (options.IsMain)
|
||||||
if (string.IsNullOrEmpty(secondaryServer) || string.Equals(secondaryServer, "0", StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
@@ -42,15 +39,18 @@ public class Program
|
|||||||
metrics.SetGaugeTo(MetricsAPI.GaugePairs, context.ClientPairs.Count());
|
metrics.SetGaugeTo(MetricsAPI.GaugePairs, context.ClientPairs.Count());
|
||||||
metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.ClientPairs.Count(p => p.IsPaused));
|
metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.ClientPairs.Count(p => p.IsPaused));
|
||||||
|
|
||||||
var options = host.Services.GetService<IOptions<ServerConfiguration>>();
|
|
||||||
var logger = host.Services.GetService<ILogger<Program>>();
|
|
||||||
logger.LogInformation("Loaded MareSynchronos Server Configuration");
|
|
||||||
logger.LogInformation(options.Value.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length == 0 || !string.Equals(args[0], "dry", StringComparison.Ordinal))
|
if (args.Length == 0 || !string.Equals(args[0], "dry", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
host.Run();
|
try
|
||||||
|
{
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Security.Claims;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
|
|
||||||
namespace MareSynchronosServer.RequirementHandlers;
|
namespace MareSynchronosServer.RequirementHandlers;
|
||||||
|
|
||||||
public class UserRequirementHandler : AuthorizationHandler<UserRequirement, HubInvocationContext>
|
public class UserRequirementHandler : AuthorizationHandler<UserRequirement, HubInvocationContext>
|
||||||
{
|
{
|
||||||
private readonly GrpcClientIdentificationService identClient;
|
private readonly IClientIdentificationService identClient;
|
||||||
private readonly MareDbContext dbContext;
|
private readonly MareDbContext dbContext;
|
||||||
private readonly ILogger<UserRequirementHandler> logger;
|
private readonly ILogger<UserRequirementHandler> logger;
|
||||||
|
|
||||||
public UserRequirementHandler(GrpcClientIdentificationService identClient, MareDbContext dbContext, ILogger<UserRequirementHandler> logger)
|
public UserRequirementHandler(IClientIdentificationService identClient, MareDbContext dbContext, ILogger<UserRequirementHandler> logger)
|
||||||
{
|
{
|
||||||
this.identClient = identClient;
|
this.identClient = identClient;
|
||||||
this.dbContext = dbContext;
|
this.dbContext = dbContext;
|
||||||
|
|||||||
@@ -2,17 +2,12 @@
|
|||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using MareSynchronosShared.Services;
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Services;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
public class GrpcClientIdentificationService : GrpcBaseService
|
public class GrpcClientIdentificationService : GrpcBaseService, IClientIdentificationService
|
||||||
{
|
{
|
||||||
private readonly string _shardName;
|
private readonly string _shardName;
|
||||||
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
||||||
@@ -22,13 +17,15 @@ public class GrpcClientIdentificationService : GrpcBaseService
|
|||||||
private readonly MareMetrics _metrics;
|
private readonly MareMetrics _metrics;
|
||||||
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, UidWithIdent> RemoteCachedIdents = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, UidWithIdent> RemoteCachedIdents = new(StringComparer.Ordinal);
|
||||||
private ConcurrentQueue<IdentChange> _identChangeQueue = new();
|
private readonly ConcurrentQueue<IdentChange> _identChangeQueue = new();
|
||||||
|
|
||||||
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient,
|
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger,
|
||||||
|
IdentificationService.IdentificationServiceClient gprcIdentClient,
|
||||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut,
|
IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut,
|
||||||
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IOptions<ServerConfiguration> configuration) : base(logger)
|
IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn,
|
||||||
|
MareMetrics metrics, IConfigurationService<ServerConfiguration> configuration) : base(logger)
|
||||||
{
|
{
|
||||||
_shardName = configuration.Value.ShardName;
|
_shardName = configuration.GetValueOrDefault(nameof(ServerConfiguration.ShardName), string.Empty);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_grpcIdentClient = gprcIdentClient;
|
_grpcIdentClient = gprcIdentClient;
|
||||||
_grpcIdentClientStreamOut = gprcIdentClientStreamOut;
|
_grpcIdentClientStreamOut = gprcIdentClientStreamOut;
|
||||||
@@ -171,7 +168,7 @@ public class GrpcClientIdentificationService : GrpcBaseService
|
|||||||
using var stream = _grpcIdentClientStreamIn.ReceiveStreamIdentStatusChange(new ServerMessage()
|
using var stream = _grpcIdentClientStreamIn.ReceiveStreamIdentStatusChange(new ServerMessage()
|
||||||
{
|
{
|
||||||
ServerId = _shardName,
|
ServerId = _shardName,
|
||||||
});
|
}, cancellationToken: cts);
|
||||||
_logger.LogInformation("Starting Receive Online Client Data stream");
|
_logger.LogInformation("Starting Receive Online Client Data stream");
|
||||||
await foreach (var cur in stream.ResponseStream.ReadAllAsync(cts).ConfigureAwait(false))
|
await foreach (var cur in stream.ResponseStream.ReadAllAsync(cts).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
@@ -201,7 +198,7 @@ public class GrpcClientIdentificationService : GrpcBaseService
|
|||||||
|
|
||||||
protected override async Task StopAsyncInternal(CancellationToken cancellationToken)
|
protected override async Task StopAsyncInternal(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }, cancellationToken: cancellationToken)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnGrpcRestore()
|
protected override async Task OnGrpcRestore()
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
|
public interface IClientIdentificationService : IHostedService
|
||||||
|
{
|
||||||
|
string GetCharacterIdentForUid(string uid);
|
||||||
|
Task<long> GetOnlineUsers();
|
||||||
|
string GetServerForUid(string uid);
|
||||||
|
bool IsOnCurrentServer(string uid);
|
||||||
|
void MarkUserOffline(string uid);
|
||||||
|
void MarkUserOnline(string uid, string charaIdent);
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
using MareSynchronosServer.Identity;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices.Identity;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
internal class IdentityService : IdentificationService.IdentificationServiceBase
|
internal class GrpcIdentityService : IdentificationService.IdentificationServiceBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<IdentityService> _logger;
|
private readonly ILogger<GrpcIdentityService> _logger;
|
||||||
private readonly IdentityHandler _handler;
|
private readonly IdentityHandler _handler;
|
||||||
|
|
||||||
public IdentityService(ILogger<IdentityService> logger, IdentityHandler handler)
|
public GrpcIdentityService(ILogger<GrpcIdentityService> logger, IdentityHandler handler)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
@@ -18,7 +18,7 @@ internal class IdentityService : IdentificationService.IdentificationServiceBase
|
|||||||
|
|
||||||
public override async Task<CharacterIdentMessage> GetIdentForUid(UidMessage request, ServerCallContext context)
|
public override async Task<CharacterIdentMessage> GetIdentForUid(UidMessage request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var result = await _handler.GetIdentForuid(request.Uid);
|
var result = await _handler.GetIdentForUid(request.Uid).ConfigureAwait(false);
|
||||||
return new CharacterIdentMessage()
|
return new CharacterIdentMessage()
|
||||||
{
|
{
|
||||||
Ident = result.CharacterIdent,
|
Ident = result.CharacterIdent,
|
||||||
@@ -26,6 +26,7 @@ internal class IdentityService : IdentificationService.IdentificationServiceBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
public override Task<OnlineUserCountResponse> GetOnlineUserCount(ServerMessage request, ServerCallContext context)
|
public override Task<OnlineUserCountResponse> GetOnlineUserCount(ServerMessage request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new OnlineUserCountResponse() { Count = _handler.GetOnlineUsers(request.ServerId) });
|
return Task.FromResult(new OnlineUserCountResponse() { Count = _handler.GetOnlineUsers(request.ServerId) });
|
||||||
@@ -66,7 +67,7 @@ internal class IdentityService : IdentificationService.IdentificationServiceBase
|
|||||||
|
|
||||||
public override async Task<Empty> SendStreamIdentStatusChange(IAsyncStreamReader<IdentChangeMessage> requestStream, ServerCallContext context)
|
public override async Task<Empty> SendStreamIdentStatusChange(IAsyncStreamReader<IdentChangeMessage> requestStream, ServerCallContext context)
|
||||||
{
|
{
|
||||||
await requestStream.MoveNext();
|
await requestStream.MoveNext().ConfigureAwait(false);
|
||||||
var server = requestStream.Current.Server;
|
var server = requestStream.Current.Server;
|
||||||
if (server == null) throw new System.Exception("First message needs to be server message");
|
if (server == null) throw new System.Exception("First message needs to be server message");
|
||||||
_handler.RegisterServerForQueue(server.ServerId);
|
_handler.RegisterServerForQueue(server.ServerId);
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using MareSynchronosServer.Identity;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
|
public class LocalClientIdentificationService : IClientIdentificationService
|
||||||
|
{
|
||||||
|
protected readonly ConcurrentDictionary<string, UidWithIdent> OnlineClients = new(StringComparer.Ordinal);
|
||||||
|
private readonly IdentityHandler _identityHandler;
|
||||||
|
private readonly string _shardName;
|
||||||
|
|
||||||
|
public LocalClientIdentificationService(IdentityHandler identityHandler, IConfigurationService<ServerConfiguration> config)
|
||||||
|
{
|
||||||
|
_identityHandler = identityHandler;
|
||||||
|
_shardName = config.GetValueOrDefault(nameof(ServerConfiguration.ShardName), string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCharacterIdentForUid(string uid)
|
||||||
|
{
|
||||||
|
return _identityHandler.GetIdentForUid(uid).Result.CharacterIdent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<long> GetOnlineUsers()
|
||||||
|
{
|
||||||
|
return Task.FromResult((long)_identityHandler.GetOnlineUsers(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetServerForUid(string uid)
|
||||||
|
{
|
||||||
|
return _identityHandler.GetIdentForUid(uid).Result.ServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOnCurrentServer(string uid)
|
||||||
|
{
|
||||||
|
return string.Equals(_identityHandler.GetIdentForUid(uid).Result.ServerId, _shardName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkUserOffline(string uid)
|
||||||
|
{
|
||||||
|
_identityHandler.RemoveIdent(uid, _shardName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkUserOnline(string uid, string charaIdent)
|
||||||
|
{
|
||||||
|
_identityHandler.SetIdent(uid, _shardName, charaIdent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,30 @@
|
|||||||
using System;
|
using MareSynchronos.API;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MareSynchronos.API;
|
|
||||||
using MareSynchronosServer.Hubs;
|
using MareSynchronosServer.Hubs;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Services;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
public class SystemInfoService : IHostedService, IDisposable
|
public class SystemInfoService : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly MareMetrics _mareMetrics;
|
private readonly MareMetrics _mareMetrics;
|
||||||
|
private readonly IConfigurationService<ServerConfiguration> _config;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly GrpcClientIdentificationService _clientIdentService;
|
private readonly IClientIdentificationService _clientIdentService;
|
||||||
private readonly ILogger<SystemInfoService> _logger;
|
private readonly ILogger<SystemInfoService> _logger;
|
||||||
private readonly IHubContext<MareHub, IMareHub> _hubContext;
|
private readonly IHubContext<MareHub, IMareHub> _hubContext;
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||||
|
|
||||||
public SystemInfoService(MareMetrics mareMetrics, IServiceProvider services, GrpcClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub, IMareHub> hubContext)
|
public SystemInfoService(MareMetrics mareMetrics, IConfigurationService<ServerConfiguration> configurationService, IServiceProvider services,
|
||||||
|
IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub, IMareHub> hubContext)
|
||||||
{
|
{
|
||||||
_mareMetrics = mareMetrics;
|
_mareMetrics = mareMetrics;
|
||||||
|
_config = configurationService;
|
||||||
_services = services;
|
_services = services;
|
||||||
_clientIdentService = clientIdentService;
|
_clientIdentService = clientIdentService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -49,14 +47,16 @@ public class SystemInfoService : IHostedService, IDisposable
|
|||||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableWorkerThreads, workerThreads);
|
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableWorkerThreads, workerThreads);
|
||||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableIOWorkerThreads, ioThreads);
|
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableIOWorkerThreads, ioThreads);
|
||||||
|
|
||||||
var secondaryServer = Environment.GetEnvironmentVariable("SECONDARY_SERVER");
|
if (_config.IsMain)
|
||||||
if (string.IsNullOrEmpty(secondaryServer) || string.Equals(secondaryServer, "0", StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
|
var onlineUsers = (int)_clientIdentService.GetOnlineUsers().Result;
|
||||||
SystemInfoDto = new SystemInfoDto()
|
SystemInfoDto = new SystemInfoDto()
|
||||||
{
|
{
|
||||||
OnlineUsers = (int)_clientIdentService.GetOnlineUsers().Result,
|
OnlineUsers = onlineUsers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Sending System Info, Online Users: {onlineUsers}", onlineUsers);
|
||||||
|
|
||||||
_hubContext.Clients.All.Client_UpdateSystemInfo(SystemInfoDto);
|
_hubContext.Clients.All.Client_UpdateSystemInfo(SystemInfoDto);
|
||||||
|
|
||||||
using var scope = _services.CreateScope();
|
using var scope = _services.CreateScope();
|
||||||
|
|||||||
@@ -1,50 +1,114 @@
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
public class CleanupService : IHostedService, IDisposable
|
public class UserCleanupService : IHostedService
|
||||||
{
|
{
|
||||||
private readonly MareMetrics metrics;
|
private readonly MareMetrics metrics;
|
||||||
private readonly ILogger<CleanupService> _logger;
|
private readonly ILogger<UserCleanupService> _logger;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly ServicesConfiguration _configuration;
|
private readonly IConfigurationService<ServerConfiguration> _configuration;
|
||||||
private Timer? _timer;
|
private CancellationTokenSource _cleanupCts;
|
||||||
|
|
||||||
public CleanupService(MareMetrics metrics, ILogger<CleanupService> logger, IServiceProvider services, IOptions<ServicesConfiguration> configuration)
|
public UserCleanupService(MareMetrics metrics, ILogger<UserCleanupService> logger, IServiceProvider services, IConfigurationService<ServerConfiguration> configuration)
|
||||||
{
|
{
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_services = services;
|
_services = services;
|
||||||
_configuration = configuration.Value;
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Cleanup Service started");
|
_logger.LogInformation("Cleanup Service started");
|
||||||
|
_cleanupCts = new();
|
||||||
|
|
||||||
_timer = new Timer(CleanUp, null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
|
_ = CleanUp(_cleanupCts.Token);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CleanUp(object state)
|
private async Task CleanUp(CancellationToken ct)
|
||||||
{
|
{
|
||||||
using var scope = _services.CreateScope();
|
while (!ct.IsCancellationRequested)
|
||||||
using var dbContext = scope.ServiceProvider.GetService<MareDbContext>()!;
|
{
|
||||||
|
using var scope = _services.CreateScope();
|
||||||
|
using var dbContext = scope.ServiceProvider.GetService<MareDbContext>()!;
|
||||||
|
|
||||||
|
CleanUpOutdatedLodestoneAuths(dbContext);
|
||||||
|
|
||||||
|
await PurgeUnusedAccounts(dbContext).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await PurgeTempInvites(dbContext).ConfigureAwait(false);
|
||||||
|
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
TimeOnly currentTime = new(now.Hour, now.Minute, now.Second);
|
||||||
|
TimeOnly futureTime = new(now.Hour, now.Minute - now.Minute % 10, 0);
|
||||||
|
var span = futureTime.AddMinutes(10) - currentTime;
|
||||||
|
|
||||||
|
_logger.LogInformation("User Cleanup Complete, next run at {date}", now.Add(span));
|
||||||
|
await Task.Delay(span, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PurgeTempInvites(MareDbContext dbContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tempInvites = await dbContext.GroupTempInvites.ToListAsync().ConfigureAwait(false);
|
||||||
|
dbContext.RemoveRange(tempInvites.Where(i => i.ExpirationDate < DateTime.UtcNow));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Error during Temp Invite purge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PurgeUnusedAccounts(MareDbContext dbContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_configuration.GetValueOrDefault(nameof(ServerConfiguration.PurgeUnusedAccounts), false))
|
||||||
|
{
|
||||||
|
var usersOlderThanDays = _configuration.GetValueOrDefault(nameof(ServerConfiguration.PurgeUnusedAccountsPeriodInDays), 14);
|
||||||
|
var maxGroupsByUser = _configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxGroupUserCount), 3);
|
||||||
|
|
||||||
|
_logger.LogInformation("Cleaning up users older than {usersOlderThanDays} days", usersOlderThanDays);
|
||||||
|
|
||||||
|
var allUsers = dbContext.Users.Where(u => string.IsNullOrEmpty(u.Alias)).ToList();
|
||||||
|
List<User> usersToRemove = new();
|
||||||
|
foreach (var user in allUsers)
|
||||||
|
{
|
||||||
|
if (user.LastLoggedIn < DateTime.UtcNow - TimeSpan.FromDays(usersOlderThanDays))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User outdated: {userUID}", user.UID);
|
||||||
|
usersToRemove.Add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var user in usersToRemove)
|
||||||
|
{
|
||||||
|
await SharedDbFunctions.PurgeUser(_logger, user, dbContext, maxGroupsByUser).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Cleaning up unauthorized users");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Error during user purge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanUpOutdatedLodestoneAuths(MareDbContext dbContext)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Cleaning up expired lodestone authentications");
|
_logger.LogInformation($"Cleaning up expired lodestone authentications");
|
||||||
@@ -65,52 +129,6 @@ public class CleanupService : IHostedService, IDisposable
|
|||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Error during expired auths cleanup");
|
_logger.LogWarning(ex, "Error during expired auths cleanup");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_configuration.PurgeUnusedAccounts)
|
|
||||||
{
|
|
||||||
var usersOlderThanDays = _configuration.PurgeUnusedAccountsPeriodInDays;
|
|
||||||
|
|
||||||
_logger.LogInformation("Cleaning up users older than {usersOlderThanDays} days", usersOlderThanDays);
|
|
||||||
|
|
||||||
var allUsers = dbContext.Users.Where(u => string.IsNullOrEmpty(u.Alias)).ToList();
|
|
||||||
List<User> usersToRemove = new();
|
|
||||||
foreach (var user in allUsers)
|
|
||||||
{
|
|
||||||
if (user.LastLoggedIn < (DateTime.UtcNow - TimeSpan.FromDays(usersOlderThanDays)))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User outdated: {userUID}", user.UID);
|
|
||||||
usersToRemove.Add(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var user in usersToRemove)
|
|
||||||
{
|
|
||||||
await PurgeUser(user, dbContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Cleaning up unauthorized users");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Error during user purge");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var tempInvites = await dbContext.GroupTempInvites.ToListAsync();
|
|
||||||
dbContext.RemoveRange(tempInvites.Where(i => i.ExpirationDate < DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Error during Temp Invite purge");
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation($"Cleanup complete");
|
|
||||||
|
|
||||||
dbContext.SaveChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PurgeUser(User user, MareDbContext dbContext)
|
public async Task PurgeUser(User user, MareDbContext dbContext)
|
||||||
@@ -152,13 +170,13 @@ public class CleanupService : IHostedService, IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_ = await SharedDbFunctions.MigrateOrDeleteGroup(dbContext, userGroupPair.Group, groupPairs, _configuration.MaxExistingGroupsByUser).ConfigureAwait(false);
|
_ = await SharedDbFunctions.MigrateOrDeleteGroup(dbContext, userGroupPair.Group, groupPairs, _configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxExistingGroupsByUser), 3)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbContext.GroupPairs.Remove(userGroupPair);
|
dbContext.GroupPairs.Remove(userGroupPair);
|
||||||
|
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("User purged: {uid}", user.UID);
|
_logger.LogInformation("User purged: {uid}", user.UID);
|
||||||
@@ -166,20 +184,15 @@ public class CleanupService : IHostedService, IDisposable
|
|||||||
dbContext.Auth.Remove(auth);
|
dbContext.Auth.Remove(auth);
|
||||||
dbContext.Users.Remove(user);
|
dbContext.Users.Remove(user);
|
||||||
|
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
metrics.DecGauge(MetricsAPI.GaugeUsersRegistered, 1);
|
metrics.DecGauge(MetricsAPI.GaugeUsersRegistered, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_timer?.Change(Timeout.Infinite, 0);
|
_cleanupCts.Cancel();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_timer?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
using System;
|
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using MareSynchronosServer.Hubs;
|
using MareSynchronosServer.Hubs;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
@@ -16,15 +10,14 @@ using MareSynchronosShared.Authentication;
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using Grpc.Net.Client.Configuration;
|
using Grpc.Net.Client.Configuration;
|
||||||
using Prometheus;
|
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
using System.Net.Http;
|
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
using MareSynchronosServer.RequirementHandlers;
|
using MareSynchronosServer.RequirementHandlers;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
|
using MareSynchronosServer.Identity;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using Prometheus;
|
||||||
|
|
||||||
namespace MareSynchronosServer;
|
namespace MareSynchronosServer;
|
||||||
|
|
||||||
@@ -41,98 +34,91 @@ public class Startup
|
|||||||
{
|
{
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
|
|
||||||
services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos"));
|
|
||||||
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
|
|
||||||
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
|
|
||||||
services.AddTransient(_ => Configuration);
|
services.AddTransient(_ => Configuration);
|
||||||
|
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddInMemoryRateLimiting();
|
|
||||||
|
|
||||||
services.AddSingleton<SystemInfoService, SystemInfoService>();
|
|
||||||
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
|
||||||
|
|
||||||
var mareConfig = Configuration.GetRequiredSection("MareSynchronos");
|
var mareConfig = Configuration.GetRequiredSection("MareSynchronos");
|
||||||
|
|
||||||
var defaultMethodConfig = new MethodConfig
|
// configure metrics
|
||||||
{
|
ConfigureMetrics(services);
|
||||||
Names = { MethodName.Default },
|
|
||||||
RetryPolicy = new RetryPolicy
|
|
||||||
{
|
|
||||||
MaxAttempts = 1000,
|
|
||||||
InitialBackoff = TimeSpan.FromSeconds(1),
|
|
||||||
MaxBackoff = TimeSpan.FromSeconds(5),
|
|
||||||
BackoffMultiplier = 1.5,
|
|
||||||
RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var noRetryConfig = new MethodConfig
|
// configure file service grpc connection
|
||||||
{
|
ConfigureFileServiceGrpcClient(services, mareConfig);
|
||||||
Names = { MethodName.Default },
|
|
||||||
RetryPolicy = null
|
|
||||||
};
|
|
||||||
|
|
||||||
services.AddSingleton<MareMetrics>(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string>
|
// configure database
|
||||||
{
|
ConfigureDatabase(services, mareConfig);
|
||||||
MetricsAPI.CounterInitializedConnections,
|
|
||||||
MetricsAPI.CounterUserPushData,
|
|
||||||
MetricsAPI.CounterUserPushDataTo,
|
|
||||||
MetricsAPI.CounterUsersRegisteredDeleted,
|
|
||||||
MetricsAPI.CounterAuthenticationCacheHits,
|
|
||||||
MetricsAPI.CounterAuthenticationFailures,
|
|
||||||
MetricsAPI.CounterAuthenticationRequests,
|
|
||||||
MetricsAPI.CounterAuthenticationSuccesses
|
|
||||||
}, new List<string>
|
|
||||||
{
|
|
||||||
MetricsAPI.GaugeAuthorizedConnections,
|
|
||||||
MetricsAPI.GaugeConnections,
|
|
||||||
MetricsAPI.GaugePairs,
|
|
||||||
MetricsAPI.GaugePairsPaused,
|
|
||||||
MetricsAPI.GaugeAvailableIOWorkerThreads,
|
|
||||||
MetricsAPI.GaugeAvailableWorkerThreads,
|
|
||||||
MetricsAPI.GaugeGroups,
|
|
||||||
MetricsAPI.GaugeGroupPairs,
|
|
||||||
MetricsAPI.GaugeGroupPairsPaused
|
|
||||||
}));
|
|
||||||
|
|
||||||
services.AddGrpcClient<FileService.FileServiceClient>(c =>
|
// configure authentication and authorization
|
||||||
|
ConfigureAuthorization(services);
|
||||||
|
|
||||||
|
// configure rate limiting
|
||||||
|
ConfigureIpRateLimiting(services);
|
||||||
|
|
||||||
|
// configure SignalR
|
||||||
|
ConfigureSignalR(services, mareConfig);
|
||||||
|
|
||||||
|
// configure mare specific services
|
||||||
|
ConfigureMareServices(services, mareConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMareServices(IServiceCollection services, IConfigurationSection mareConfig)
|
||||||
|
{
|
||||||
|
bool isMainServer = mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerGrpcAddress), defaultValue: null) == null;
|
||||||
|
|
||||||
|
services.Configure<ServerConfiguration>(mareConfig);
|
||||||
|
services.Configure<MareConfigurationBase>(mareConfig);
|
||||||
|
services.Configure<MareConfigurationAuthBase>(mareConfig);
|
||||||
|
|
||||||
|
services.AddSingleton<SystemInfoService>();
|
||||||
|
services.AddSingleton<IUserIdProvider, IdBasedUserIdProvider>();
|
||||||
|
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||||
|
// configure services based on main server status
|
||||||
|
ConfigureIdentityServices(services, mareConfig, isMainServer);
|
||||||
|
|
||||||
|
if (isMainServer)
|
||||||
{
|
{
|
||||||
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServerConfiguration.StaticFileServiceAddress)));
|
services.AddSingleton<UserCleanupService>();
|
||||||
}).ConfigureChannel(c =>
|
services.AddHostedService(provider => provider.GetService<UserCleanupService>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureSignalR(IServiceCollection services, IConfigurationSection mareConfig)
|
||||||
|
{
|
||||||
|
var signalRServiceBuilder = services.AddSignalR(hubOptions =>
|
||||||
{
|
{
|
||||||
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
|
hubOptions.MaximumReceiveMessageSize = long.MaxValue;
|
||||||
});
|
hubOptions.EnableDetailedErrors = true;
|
||||||
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
|
hubOptions.MaximumParallelInvocationsPerClient = 10;
|
||||||
{
|
hubOptions.StreamBufferCapacity = 200;
|
||||||
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServerConfiguration.ServiceAddress)));
|
|
||||||
}).ConfigureChannel(c =>
|
hubOptions.AddFilter<SignalRLimitFilter>();
|
||||||
{
|
|
||||||
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
|
|
||||||
c.HttpHandler = new SocketsHttpHandler()
|
|
||||||
{
|
|
||||||
EnableMultipleHttp2Connections = true
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// configure redis for SignalR
|
||||||
|
var redis = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
|
||||||
|
if (!string.IsNullOrEmpty(redis))
|
||||||
|
{
|
||||||
|
signalRServiceBuilder.AddStackExchangeRedis(redis, options =>
|
||||||
|
{
|
||||||
|
options.Configuration.ChannelPrefix = "MareSynchronos";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureIpRateLimiting(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
|
||||||
|
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
|
||||||
|
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
||||||
|
services.AddMemoryCache();
|
||||||
|
services.AddInMemoryRateLimiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureAuthorization(IServiceCollection services)
|
||||||
|
{
|
||||||
services.AddSingleton<SecretKeyAuthenticatorService>();
|
services.AddSingleton<SecretKeyAuthenticatorService>();
|
||||||
services.AddSingleton<GrpcClientIdentificationService>();
|
|
||||||
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
|
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
|
||||||
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
|
||||||
|
|
||||||
services.AddDbContextPool<MareDbContext>(options =>
|
|
||||||
{
|
|
||||||
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
|
||||||
{
|
|
||||||
builder.MigrationsHistoryTable("_efmigrationshistory", "public");
|
|
||||||
builder.MigrationsAssembly("MareSynchronosShared");
|
|
||||||
}).UseSnakeCaseNamingConvention();
|
|
||||||
options.EnableThreadSafetyChecks(false);
|
|
||||||
}, mareConfig.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024));
|
|
||||||
|
|
||||||
services.AddAuthentication(SecretKeyAuthenticationHandler.AuthScheme)
|
services.AddAuthentication(SecretKeyAuthenticationHandler.AuthScheme)
|
||||||
.AddScheme<AuthenticationSchemeOptions, SecretKeyAuthenticationHandler>(SecretKeyAuthenticationHandler.AuthScheme, options => { options.Validate(); });
|
.AddScheme<AuthenticationSchemeOptions, SecretKeyAuthenticationHandler>(SecretKeyAuthenticationHandler.AuthScheme, options => { options.Validate(); });
|
||||||
|
|
||||||
services.AddAuthorization(options =>
|
services.AddAuthorization(options =>
|
||||||
{
|
{
|
||||||
@@ -157,44 +143,127 @@ public class Startup
|
|||||||
policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Moderator | UserRequirements.Administrator));
|
policy.AddRequirements(new UserRequirement(UserRequirements.Identified | UserRequirements.Moderator | UserRequirements.Administrator));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
|
||||||
|
|
||||||
var signalRServiceBuilder = services.AddSignalR(hubOptions =>
|
|
||||||
{
|
|
||||||
hubOptions.MaximumReceiveMessageSize = long.MaxValue;
|
|
||||||
hubOptions.EnableDetailedErrors = true;
|
|
||||||
hubOptions.MaximumParallelInvocationsPerClient = 10;
|
|
||||||
hubOptions.StreamBufferCapacity = 200;
|
|
||||||
|
|
||||||
hubOptions.AddFilter<SignalRLimitFilter>();
|
|
||||||
});
|
|
||||||
|
|
||||||
// add redis related options
|
|
||||||
var redis = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
|
|
||||||
if (!string.IsNullOrEmpty(redis))
|
|
||||||
{
|
|
||||||
signalRServiceBuilder.AddStackExchangeRedis(redis, options =>
|
|
||||||
{
|
|
||||||
options.Configuration.ChannelPrefix = "MareSynchronos";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
private void ConfigureDatabase(IServiceCollection services, IConfigurationSection mareConfig)
|
||||||
{
|
{
|
||||||
if (env.IsDevelopment())
|
services.AddDbContextPool<MareDbContext>(options =>
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
||||||
app.UseMigrationsEndPoint();
|
{
|
||||||
|
builder.MigrationsHistoryTable("_efmigrationshistory", "public");
|
||||||
|
builder.MigrationsAssembly("MareSynchronosShared");
|
||||||
|
}).UseSnakeCaseNamingConvention();
|
||||||
|
options.EnableThreadSafetyChecks(false);
|
||||||
|
}, mareConfig.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMetrics(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MareMetrics>(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string>
|
||||||
|
{
|
||||||
|
MetricsAPI.CounterInitializedConnections,
|
||||||
|
MetricsAPI.CounterUserPushData,
|
||||||
|
MetricsAPI.CounterUserPushDataTo,
|
||||||
|
MetricsAPI.CounterUsersRegisteredDeleted,
|
||||||
|
MetricsAPI.CounterAuthenticationCacheHits,
|
||||||
|
MetricsAPI.CounterAuthenticationFailures,
|
||||||
|
MetricsAPI.CounterAuthenticationRequests,
|
||||||
|
MetricsAPI.CounterAuthenticationSuccesses,
|
||||||
|
}, new List<string>
|
||||||
|
{
|
||||||
|
MetricsAPI.GaugeAuthorizedConnections,
|
||||||
|
MetricsAPI.GaugeConnections,
|
||||||
|
MetricsAPI.GaugePairs,
|
||||||
|
MetricsAPI.GaugePairsPaused,
|
||||||
|
MetricsAPI.GaugeAvailableIOWorkerThreads,
|
||||||
|
MetricsAPI.GaugeAvailableWorkerThreads,
|
||||||
|
MetricsAPI.GaugeGroups,
|
||||||
|
MetricsAPI.GaugeGroupPairs,
|
||||||
|
MetricsAPI.GaugeGroupPairsPaused,
|
||||||
|
MetricsAPI.GaugeUsersRegistered
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureIdentityServices(IServiceCollection services, IConfigurationSection mareConfig, bool isMainServer)
|
||||||
|
{
|
||||||
|
if (!isMainServer)
|
||||||
|
{
|
||||||
|
var noRetryConfig = new MethodConfig
|
||||||
|
{
|
||||||
|
Names = { MethodName.Default },
|
||||||
|
RetryPolicy = null
|
||||||
|
};
|
||||||
|
|
||||||
|
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServerConfiguration.MainServerGrpcAddress)));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
|
||||||
|
c.HttpHandler = new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
EnableMultipleHttp2Connections = true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddGrpcClient<ConfigurationService.ConfigurationServiceClient>(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServerConfiguration.MainServerGrpcAddress)));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
|
||||||
|
c.HttpHandler = new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
EnableMultipleHttp2Connections = true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<IClientIdentificationService, GrpcClientIdentificationService>();
|
||||||
|
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
||||||
|
services.AddSingleton<IConfigurationService<ServerConfiguration>, MareConfigurationServiceClient<ServerConfiguration>>();
|
||||||
|
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceClient<MareConfigurationAuthBase>>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error");
|
services.AddSingleton<IdentityHandler>();
|
||||||
app.UseHsts();
|
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
|
||||||
|
services.AddSingleton<IConfigurationService<ServerConfiguration>, MareConfigurationServiceServer<ServerConfiguration>>();
|
||||||
|
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceServer<MareConfigurationAuthBase>>();
|
||||||
|
|
||||||
|
services.AddGrpc();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureFileServiceGrpcClient(IServiceCollection services, IConfigurationSection mareConfig)
|
||||||
|
{
|
||||||
|
var defaultMethodConfig = new MethodConfig
|
||||||
|
{
|
||||||
|
Names = { MethodName.Default },
|
||||||
|
RetryPolicy = new RetryPolicy
|
||||||
|
{
|
||||||
|
MaxAttempts = 1000,
|
||||||
|
InitialBackoff = TimeSpan.FromSeconds(1),
|
||||||
|
MaxBackoff = TimeSpan.FromSeconds(5),
|
||||||
|
BackoffMultiplier = 1.5,
|
||||||
|
RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
services.AddGrpcClient<FileService.FileServiceClient>((serviceProvider, c) =>
|
||||||
|
{
|
||||||
|
c.Address = serviceProvider.GetRequiredService<IConfigurationService<ServerConfiguration>>()
|
||||||
|
.GetValue<Uri>(nameof(ServerConfiguration.StaticFileServiceAddress));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Running Configure");
|
||||||
|
|
||||||
|
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>();
|
||||||
|
|
||||||
app.UseIpRateLimiting();
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
@@ -202,7 +271,7 @@ public class Startup
|
|||||||
|
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
|
|
||||||
var metricServer = new KestrelMetricServer(4980);
|
var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4981));
|
||||||
metricServer.Start();
|
metricServer.Start();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
@@ -216,6 +285,12 @@ public class Startup
|
|||||||
options.TransportMaxBufferSize = 5242880;
|
options.TransportMaxBufferSize = 5242880;
|
||||||
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
|
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (config.IsMain)
|
||||||
|
{
|
||||||
|
endpoints.MapGrpcService<GrpcIdentityService>().AllowAnonymous();
|
||||||
|
endpoints.MapGrpcService<GrpcConfigurationService<ServerConfiguration>>().AllowAnonymous();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Security.Claims;
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace MareSynchronosServer.Utils;
|
namespace MareSynchronosServer.Utils;
|
||||||
@@ -8,6 +7,6 @@ public class IdBasedUserIdProvider : IUserIdProvider
|
|||||||
{
|
{
|
||||||
public string GetUserId(HubConnectionContext context)
|
public string GetUserId(HubConnectionContext context)
|
||||||
{
|
{
|
||||||
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, System.StringComparison.Ordinal))?.Value;
|
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using MareSynchronosServer.Hubs;
|
using MareSynchronosServer.Hubs;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace MareSynchronosServer.Utils;
|
namespace MareSynchronosServer.Utils;
|
||||||
@@ -14,6 +13,7 @@ public class MareHubLogger
|
|||||||
_hub = hub;
|
_hub = hub;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object[] Args(params object[] args)
|
public static object[] Args(params object[] args)
|
||||||
{
|
{
|
||||||
return args;
|
return args;
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
namespace MareSynchronosServer.Utils;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer.Utils;
|
|
||||||
|
|
||||||
public record PausedEntry
|
public record PausedEntry
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,41 +1,34 @@
|
|||||||
using System;
|
using Discord;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord;
|
|
||||||
using Discord.Interactions;
|
using Discord.Interactions;
|
||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using MareSynchronosServices.Identity;
|
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using static MareSynchronosShared.Protos.IdentificationService;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
internal class DiscordBot : IHostedService
|
internal class DiscordBot : IHostedService
|
||||||
{
|
{
|
||||||
private readonly DiscordBotServices _botServices;
|
private readonly DiscordBotServices _botServices;
|
||||||
private readonly IdentityHandler _identityHandler;
|
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly ServicesConfiguration _configuration;
|
private readonly IConfigurationService<ServicesConfiguration> _configurationService;
|
||||||
private readonly ILogger<DiscordBot> _logger;
|
private readonly ILogger<DiscordBot> _logger;
|
||||||
|
private readonly IdentificationServiceClient _identificationServiceClient;
|
||||||
private readonly DiscordSocketClient _discordClient;
|
private readonly DiscordSocketClient _discordClient;
|
||||||
private CancellationTokenSource? _updateStatusCts;
|
private CancellationTokenSource? _updateStatusCts;
|
||||||
private CancellationTokenSource? _vanityUpdateCts;
|
private CancellationTokenSource? _vanityUpdateCts;
|
||||||
|
|
||||||
public DiscordBot(DiscordBotServices botServices, IdentityHandler identityHandler, IServiceProvider services, IOptions<ServicesConfiguration> configuration, ILogger<DiscordBot> logger)
|
public DiscordBot(DiscordBotServices botServices, IServiceProvider services, IConfigurationService<ServicesConfiguration> configuration,
|
||||||
|
ILogger<DiscordBot> logger, IdentificationServiceClient identificationServiceClient)
|
||||||
{
|
{
|
||||||
_botServices = botServices;
|
_botServices = botServices;
|
||||||
_identityHandler = identityHandler;
|
|
||||||
_services = services;
|
_services = services;
|
||||||
_configuration = configuration.Value;
|
_configurationService = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
this._identificationServiceClient = identificationServiceClient;
|
||||||
_discordClient = new(new DiscordSocketConfig()
|
_discordClient = new(new DiscordSocketConfig()
|
||||||
{
|
{
|
||||||
DefaultRetryMode = RetryMode.AlwaysRetry
|
DefaultRetryMode = RetryMode.AlwaysRetry
|
||||||
@@ -72,7 +65,8 @@ internal class DiscordBot : IHostedService
|
|||||||
_vanityUpdateCts = new();
|
_vanityUpdateCts = new();
|
||||||
var guild = (await _discordClient.Rest.GetGuildsAsync()).First();
|
var guild = (await _discordClient.Rest.GetGuildsAsync()).First();
|
||||||
var commands = await guild.GetApplicationCommandsAsync();
|
var commands = await guild.GetApplicationCommandsAsync();
|
||||||
var vanityCommandId = commands.First(c => c.Name == "setvanityuid").Id;
|
var appId = await _discordClient.GetApplicationInfoAsync().ConfigureAwait(false);
|
||||||
|
var vanityCommandId = commands.First(c => c.ApplicationId == appId.Id && c.Name == "setvanityuid").Id;
|
||||||
|
|
||||||
while (!_vanityUpdateCts.IsCancellationRequested)
|
while (!_vanityUpdateCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -176,18 +170,19 @@ internal class DiscordBot : IHostedService
|
|||||||
_updateStatusCts = new();
|
_updateStatusCts = new();
|
||||||
while (!_updateStatusCts.IsCancellationRequested)
|
while (!_updateStatusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var onlineUsers = _identityHandler.GetOnlineUsers(string.Empty);
|
var onlineUsers = await _identificationServiceClient.GetOnlineUserCountAsync(new MareSynchronosShared.Protos.ServerMessage());
|
||||||
_logger.LogInformation("Users online: " + onlineUsers);
|
_logger.LogInformation("Users online: " + onlineUsers.Count);
|
||||||
await _discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
|
await _discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers.Count + " Users")).ConfigureAwait(false);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_configuration.DiscordBotToken))
|
var token = _configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty);
|
||||||
|
if (!string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
await _discordClient.LoginAsync(TokenType.Bot, _configuration.DiscordBotToken).ConfigureAwait(false);
|
await _discordClient.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
|
||||||
await _discordClient.StartAsync().ConfigureAwait(false);
|
await _discordClient.StartAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_discordClient.Ready += DiscordClient_Ready;
|
_discordClient.Ready += DiscordClient_Ready;
|
||||||
@@ -199,7 +194,7 @@ internal class DiscordBot : IHostedService
|
|||||||
|
|
||||||
public async Task StopAsync(CancellationToken cancellationToken)
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_configuration.DiscordBotToken))
|
if (!string.IsNullOrEmpty(_configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty)))
|
||||||
{
|
{
|
||||||
await _botServices.Stop();
|
await _botServices.Stop();
|
||||||
_updateStatusCts?.Cancel();
|
_updateStatusCts?.Cancel();
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
public class DiscordBotServices
|
public class DiscordBotServices
|
||||||
{
|
{
|
||||||
public readonly ConcurrentQueue<KeyValuePair<ulong, Action<IServiceProvider>>> verificationQueue = new();
|
public ConcurrentQueue<KeyValuePair<ulong, Action<IServiceProvider>>> VerificationQueue { get; } = new();
|
||||||
public ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
public ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
||||||
public ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
public ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
||||||
public ConcurrentDictionary<ulong, string> DiscordLodestoneMapping = new();
|
public ConcurrentDictionary<ulong, string> DiscordLodestoneMapping = new();
|
||||||
@@ -19,29 +13,27 @@ public class DiscordBotServices
|
|||||||
public readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "de" };
|
public readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "de" };
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
public ServicesConfiguration Configuration { get; init; }
|
|
||||||
public ILogger<DiscordBotServices> Logger { get; init; }
|
public ILogger<DiscordBotServices> Logger { get; init; }
|
||||||
public MareMetrics Metrics { get; init; }
|
public MareMetrics Metrics { get; init; }
|
||||||
public Random Random { get; init; }
|
|
||||||
private CancellationTokenSource? verificationTaskCts;
|
private CancellationTokenSource? verificationTaskCts;
|
||||||
|
|
||||||
public DiscordBotServices(IOptions<ServicesConfiguration> configuration, IServiceProvider serviceProvider, ILogger<DiscordBotServices> logger, MareMetrics metrics)
|
public DiscordBotServices(IServiceProvider serviceProvider, ILogger<DiscordBotServices> logger, MareMetrics metrics)
|
||||||
{
|
{
|
||||||
Configuration = configuration.Value;
|
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
Metrics = metrics;
|
Metrics = metrics;
|
||||||
Random = new();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start()
|
public Task Start()
|
||||||
{
|
{
|
||||||
_ = ProcessVerificationQueue();
|
_ = ProcessVerificationQueue();
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Stop()
|
public Task Stop()
|
||||||
{
|
{
|
||||||
verificationTaskCts?.Cancel();
|
verificationTaskCts?.Cancel();
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessVerificationQueue()
|
private async Task ProcessVerificationQueue()
|
||||||
@@ -49,7 +41,7 @@ public class DiscordBotServices
|
|||||||
verificationTaskCts = new CancellationTokenSource();
|
verificationTaskCts = new CancellationTokenSource();
|
||||||
while (!verificationTaskCts.IsCancellationRequested)
|
while (!verificationTaskCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (verificationQueue.TryDequeue(out var queueitem))
|
if (VerificationQueue.TryDequeue(out var queueitem))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -61,8 +53,8 @@ public class DiscordBotServices
|
|||||||
{
|
{
|
||||||
Logger.LogError(e, "Error during queue work");
|
Logger.LogError(e, "Error during queue work");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(2), verificationTaskCts.Token).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(2), verificationTaskCts.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,15 @@
|
|||||||
using Discord.Interactions;
|
using Discord.Interactions;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using System.Linq;
|
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using MareSynchronosServices.Identity;
|
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using System.Net.Http;
|
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using System.Collections.Generic;
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using static MareSynchronosShared.Protos.IdentificationService;
|
||||||
|
using static System.Formats.Asn1.AsnWriter;
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
@@ -30,22 +25,30 @@ public class LodestoneModal : IModal
|
|||||||
|
|
||||||
public class MareModule : InteractionModuleBase
|
public class MareModule : InteractionModuleBase
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<MareModule> _logger;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly DiscordBotServices _botServices;
|
private readonly DiscordBotServices _botServices;
|
||||||
private readonly IdentityHandler _identityHandler;
|
private readonly IdentificationServiceClient _identificationServiceClient;
|
||||||
private readonly CleanupService _cleanupService;
|
private readonly IConfigurationService<ServerConfiguration> _mareClientConfigurationService;
|
||||||
|
private Random random = new();
|
||||||
|
|
||||||
public MareModule(IServiceProvider services, DiscordBotServices botServices, IdentityHandler identityHandler, CleanupService cleanupService)
|
public MareModule(ILogger<MareModule> logger, IServiceProvider services, DiscordBotServices botServices,
|
||||||
|
IdentificationServiceClient identificationServiceClient, IConfigurationService<ServerConfiguration> mareClientConfigurationService)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_services = services;
|
_services = services;
|
||||||
_botServices = botServices;
|
_botServices = botServices;
|
||||||
_identityHandler = identityHandler;
|
_identificationServiceClient = identificationServiceClient;
|
||||||
_cleanupService = cleanupService;
|
_mareClientConfigurationService = mareClientConfigurationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("register", "Starts the registration process for the Mare Synchronos server of this Discord")]
|
[SlashCommand("register", "Starts the registration process for the Mare Synchronos server of this Discord")]
|
||||||
public async Task Register([Summary("overwrite", "Overwrites your old account")] bool overwrite = false)
|
public async Task Register([Summary("overwrite", "Overwrites your old account")] bool overwrite = false)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(Register),
|
||||||
|
string.Join(",", new[] { $"{nameof(overwrite)}:{overwrite}" }));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
if (overwrite)
|
if (overwrite)
|
||||||
@@ -60,6 +63,10 @@ public class MareModule : InteractionModuleBase
|
|||||||
[SlashCommand("setvanityuid", "Sets your Vanity UID.")]
|
[SlashCommand("setvanityuid", "Sets your Vanity UID.")]
|
||||||
public async Task SetVanityUid([Summary("vanity_uid", "Desired Vanity UID")] string vanityUid)
|
public async Task SetVanityUid([Summary("vanity_uid", "Desired Vanity UID")] string vanityUid)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(SetVanityUid),
|
||||||
|
string.Join(",", new[] { $"{nameof(vanityUid)}:{vanityUid}" }));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
EmbedBuilder eb = new();
|
EmbedBuilder eb = new();
|
||||||
@@ -75,6 +82,10 @@ public class MareModule : InteractionModuleBase
|
|||||||
[Summary("syncshell_id", "Syncshell ID")] string syncshellId,
|
[Summary("syncshell_id", "Syncshell ID")] string syncshellId,
|
||||||
[Summary("vanity_syncshell_id", "Desired Vanity Syncshell ID")] string vanityId)
|
[Summary("vanity_syncshell_id", "Desired Vanity Syncshell ID")] string vanityId)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(SetSyncshellVanityId),
|
||||||
|
string.Join(",", new[] { $"{nameof(syncshellId)}:{syncshellId}", $"{nameof(vanityId)}:{vanityId}" }));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
EmbedBuilder eb = new();
|
EmbedBuilder eb = new();
|
||||||
@@ -88,10 +99,12 @@ public class MareModule : InteractionModuleBase
|
|||||||
[SlashCommand("verify", "Finishes the registration process for the Mare Synchronos server of this Discord")]
|
[SlashCommand("verify", "Finishes the registration process for the Mare Synchronos server of this Discord")]
|
||||||
public async Task Verify()
|
public async Task Verify()
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(Verify));
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
EmbedBuilder eb = new();
|
EmbedBuilder eb = new();
|
||||||
if (_botServices.verificationQueue.Any(u => u.Key == Context.User.Id))
|
if (_botServices.VerificationQueue.Any(u => u.Key == Context.User.Id))
|
||||||
{
|
{
|
||||||
eb.WithTitle("Already queued for verfication");
|
eb.WithTitle("Already queued for verfication");
|
||||||
eb.WithDescription("You are already queued for verification. Please wait.");
|
eb.WithDescription("You are already queued for verification. Please wait.");
|
||||||
@@ -106,7 +119,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await DeferAsync(ephemeral: true).ConfigureAwait(false);
|
await DeferAsync(ephemeral: true).ConfigureAwait(false);
|
||||||
_botServices.verificationQueue.Enqueue(new KeyValuePair<ulong, Action<IServiceProvider>>(Context.User.Id, async (sp) => await HandleVerifyAsync((SocketSlashCommand)Context.Interaction, sp)));
|
_botServices.VerificationQueue.Enqueue(new KeyValuePair<ulong, Action<IServiceProvider>>(Context.User.Id, async (sp) => await HandleVerifyAsync((SocketSlashCommand)Context.Interaction, sp)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -114,10 +127,12 @@ public class MareModule : InteractionModuleBase
|
|||||||
[SlashCommand("verify_relink", "Finishes the relink process for your user on the Mare Synchronos server of this Discord")]
|
[SlashCommand("verify_relink", "Finishes the relink process for your user on the Mare Synchronos server of this Discord")]
|
||||||
public async Task VerifyRelink()
|
public async Task VerifyRelink()
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(VerifyRelink));
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
EmbedBuilder eb = new();
|
EmbedBuilder eb = new();
|
||||||
if (_botServices.verificationQueue.Any(u => u.Key == Context.User.Id))
|
if (_botServices.VerificationQueue.Any(u => u.Key == Context.User.Id))
|
||||||
{
|
{
|
||||||
eb.WithTitle("Already queued for verfication");
|
eb.WithTitle("Already queued for verfication");
|
||||||
eb.WithDescription("You are already queued for verification. Please wait.");
|
eb.WithDescription("You are already queued for verification. Please wait.");
|
||||||
@@ -132,7 +147,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await DeferAsync(ephemeral: true).ConfigureAwait(false);
|
await DeferAsync(ephemeral: true).ConfigureAwait(false);
|
||||||
_botServices.verificationQueue.Enqueue(new KeyValuePair<ulong, Action<IServiceProvider>>(Context.User.Id, async (sp) => await HandleVerifyRelinkAsync((SocketSlashCommand)Context.Interaction, sp)));
|
_botServices.VerificationQueue.Enqueue(new KeyValuePair<ulong, Action<IServiceProvider>>(Context.User.Id, async (sp) => await HandleVerifyRelinkAsync((SocketSlashCommand)Context.Interaction, sp)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,6 +155,8 @@ public class MareModule : InteractionModuleBase
|
|||||||
[SlashCommand("recover", "Allows you to recover your account by generating a new secret key")]
|
[SlashCommand("recover", "Allows you to recover your account by generating a new secret key")]
|
||||||
public async Task Recover()
|
public async Task Recover()
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(Recover));
|
||||||
await RespondWithModalAsync<LodestoneModal>("recover_modal").ConfigureAwait(false);
|
await RespondWithModalAsync<LodestoneModal>("recover_modal").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +165,9 @@ public class MareModule : InteractionModuleBase
|
|||||||
[Summary("discord_user", "ADMIN ONLY: Discord User to check for")] IUser? discordUser = null,
|
[Summary("discord_user", "ADMIN ONLY: Discord User to check for")] IUser? discordUser = null,
|
||||||
[Summary("uid", "ADMIN ONLY: UID to check for")] string? uid = null)
|
[Summary("uid", "ADMIN ONLY: UID to check for")] string? uid = null)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(UserInfo));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
EmbedBuilder eb = new();
|
EmbedBuilder eb = new();
|
||||||
@@ -161,12 +181,32 @@ public class MareModule : InteractionModuleBase
|
|||||||
[SlashCommand("relink", "Allows you to link a new Discord account to an existing Mare account")]
|
[SlashCommand("relink", "Allows you to link a new Discord account to an existing Mare account")]
|
||||||
public async Task Relink()
|
public async Task Relink()
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(Relink));
|
||||||
await RespondWithModalAsync<LodestoneModal>("relink_modal").ConfigureAwait(false);
|
await RespondWithModalAsync<LodestoneModal>("relink_modal").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SlashCommand("useradd", "ADMIN ONLY: add a user unconditionally to the Database")]
|
||||||
|
public async Task UserAdd([Summary("desired_uid", "Desired UID")] string desiredUid)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("SlashCommand:{userId}:{Method}:{params}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(UserAdd),
|
||||||
|
string.Join(",", new[] { $"{nameof(desiredUid)}:{desiredUid}" }));
|
||||||
|
|
||||||
|
await TryRespondAsync(async () =>
|
||||||
|
{
|
||||||
|
var embed = await HandleUserAdd(desiredUid, Context.User.Id);
|
||||||
|
|
||||||
|
await RespondAsync(embeds: new[] { embed }, ephemeral: true).ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[ModalInteraction("recover_modal")]
|
[ModalInteraction("recover_modal")]
|
||||||
public async Task RecoverModal(LodestoneModal modal)
|
public async Task RecoverModal(LodestoneModal modal)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Modal:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(RecoverModal));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
var embed = await HandleRecoverModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
var embed = await HandleRecoverModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
||||||
@@ -177,6 +217,9 @@ public class MareModule : InteractionModuleBase
|
|||||||
[ModalInteraction("register_modal")]
|
[ModalInteraction("register_modal")]
|
||||||
public async Task RegisterModal(LodestoneModal modal)
|
public async Task RegisterModal(LodestoneModal modal)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Modal:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(RegisterModal));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
var embed = await HandleRegisterModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
var embed = await HandleRegisterModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
||||||
@@ -187,6 +230,9 @@ public class MareModule : InteractionModuleBase
|
|||||||
[ModalInteraction("relink_modal")]
|
[ModalInteraction("relink_modal")]
|
||||||
public async Task RelinkModal(LodestoneModal modal)
|
public async Task RelinkModal(LodestoneModal modal)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Modal:{userId}:{Method}",
|
||||||
|
Context.Client.CurrentUser.Id, nameof(RelinkModal));
|
||||||
|
|
||||||
await TryRespondAsync(async () =>
|
await TryRespondAsync(async () =>
|
||||||
{
|
{
|
||||||
var embed = await HandleRelinkModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
var embed = await HandleRelinkModalAsync(modal, Context.User.Id).ConfigureAwait(false);
|
||||||
@@ -194,6 +240,51 @@ public class MareModule : InteractionModuleBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Embed> HandleUserAdd(string desiredUid, ulong discordUserId)
|
||||||
|
{
|
||||||
|
var embed = new EmbedBuilder();
|
||||||
|
|
||||||
|
using var scope = _services.CreateScope();
|
||||||
|
using var db = scope.ServiceProvider.GetService<MareDbContext>();
|
||||||
|
if (!(await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(a => a.DiscordId == discordUserId))?.User?.IsAdmin ?? true)
|
||||||
|
{
|
||||||
|
embed.WithTitle("Failed to add user");
|
||||||
|
embed.WithDescription("No permission");
|
||||||
|
}
|
||||||
|
else if (db.Users.Any(u => u.UID == desiredUid || u.Alias == desiredUid))
|
||||||
|
{
|
||||||
|
embed.WithTitle("Failed to add user");
|
||||||
|
embed.WithDescription("Already in Database");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
User newUser = new()
|
||||||
|
{
|
||||||
|
IsAdmin = false,
|
||||||
|
IsModerator = false,
|
||||||
|
LastLoggedIn = DateTime.UtcNow,
|
||||||
|
UID = desiredUid,
|
||||||
|
};
|
||||||
|
|
||||||
|
var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString());
|
||||||
|
var auth = new Auth()
|
||||||
|
{
|
||||||
|
HashedKey = StringUtils.Sha256String(computedHash),
|
||||||
|
User = newUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.Users.AddAsync(newUser);
|
||||||
|
await db.Auth.AddAsync(auth);
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
embed.WithTitle("Successfully added " + desiredUid);
|
||||||
|
embed.WithDescription("Secret Key: " + computedHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed.Build();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TryRespondAsync(Action act)
|
private async Task TryRespondAsync(Action act)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -258,7 +349,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
var lodestoneUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == userToCheckForDiscordId).ConfigureAwait(false);
|
var lodestoneUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == userToCheckForDiscordId).ConfigureAwait(false);
|
||||||
var dbUser = lodestoneUser.User;
|
var dbUser = lodestoneUser.User;
|
||||||
var auth = await db.Auth.SingleOrDefaultAsync(u => u.UserUID == dbUser.UID).ConfigureAwait(false);
|
var auth = await db.Auth.SingleOrDefaultAsync(u => u.UserUID == dbUser.UID).ConfigureAwait(false);
|
||||||
var identity = await _identityHandler.GetIdentForuid(dbUser.UID).ConfigureAwait(false);
|
var identity = await _identificationServiceClient.GetIdentForUidAsync(new MareSynchronosShared.Protos.UidMessage { Uid = dbUser.UID });
|
||||||
var groups = await db.Groups.Where(g => g.OwnerUID == dbUser.UID).ToListAsync().ConfigureAwait(false);
|
var groups = await db.Groups.Where(g => g.OwnerUID == dbUser.UID).ToListAsync().ConfigureAwait(false);
|
||||||
var groupsJoined = await db.GroupPairs.Where(g => g.GroupUserUID == dbUser.UID).ToListAsync().ConfigureAwait(false);
|
var groupsJoined = await db.GroupPairs.Where(g => g.GroupUserUID == dbUser.UID).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -271,7 +362,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
eb.AddField("Vanity UID", dbUser.Alias);
|
eb.AddField("Vanity UID", dbUser.Alias);
|
||||||
}
|
}
|
||||||
eb.AddField("Last Online (UTC)", dbUser.LastLoggedIn.ToString("U"));
|
eb.AddField("Last Online (UTC)", dbUser.LastLoggedIn.ToString("U"));
|
||||||
eb.AddField("Currently online: ", !string.IsNullOrEmpty(identity.CharacterIdent));
|
eb.AddField("Currently online: ", !string.IsNullOrEmpty(identity.Ident));
|
||||||
eb.AddField("Hashed Secret Key", auth.HashedKey);
|
eb.AddField("Hashed Secret Key", auth.HashedKey);
|
||||||
eb.AddField("Joined Syncshells", groupsJoined.Count);
|
eb.AddField("Joined Syncshells", groupsJoined.Count);
|
||||||
eb.AddField("Owned Syncshells", groups.Count);
|
eb.AddField("Owned Syncshells", groups.Count);
|
||||||
@@ -285,9 +376,9 @@ public class MareModule : InteractionModuleBase
|
|||||||
eb.AddField("Owned Syncshell " + group.GID + " User Count", syncShellUserCount);
|
eb.AddField("Owned Syncshell " + group.GID + " User Count", syncShellUserCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdminCall && !string.IsNullOrEmpty(identity.CharacterIdent))
|
if (isAdminCall && !string.IsNullOrEmpty(identity.Ident))
|
||||||
{
|
{
|
||||||
eb.AddField("Character Ident", identity.CharacterIdent);
|
eb.AddField("Character Ident", identity.Ident);
|
||||||
}
|
}
|
||||||
|
|
||||||
return eb;
|
return eb;
|
||||||
@@ -635,7 +726,9 @@ public class MareModule : InteractionModuleBase
|
|||||||
{
|
{
|
||||||
if (discordAuthedUser.User != null)
|
if (discordAuthedUser.User != null)
|
||||||
{
|
{
|
||||||
await _cleanupService.PurgeUser(discordAuthedUser.User, db);
|
var maxGroupsByUser = _mareClientConfigurationService.GetValueOrDefault(nameof(ServerConfiguration.MaxGroupUserCount), 3);
|
||||||
|
|
||||||
|
await SharedDbFunctions.PurgeUser(_logger, discordAuthedUser.User, db, maxGroupsByUser);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -657,7 +750,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == cmd.User.Id);
|
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == cmd.User.Id);
|
||||||
if (lodestoneAuth != null && _botServices.DiscordRelinkLodestoneMapping.ContainsKey(cmd.User.Id))
|
if (lodestoneAuth != null && _botServices.DiscordRelinkLodestoneMapping.ContainsKey(cmd.User.Id))
|
||||||
{
|
{
|
||||||
var randomServer = _botServices.LodestoneServers[_botServices.Random.Next(_botServices.LodestoneServers.Length)];
|
var randomServer = _botServices.LodestoneServers[random.Next(_botServices.LodestoneServers.Length)];
|
||||||
var response = await req.GetAsync($"https://{randomServer}.finalfantasyxiv.com/lodestone/character/{_botServices.DiscordRelinkLodestoneMapping[cmd.User.Id]}").ConfigureAwait(false);
|
var response = await req.GetAsync($"https://{randomServer}.finalfantasyxiv.com/lodestone/character/{_botServices.DiscordRelinkLodestoneMapping[cmd.User.Id]}").ConfigureAwait(false);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -731,7 +824,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == cmd.User.Id);
|
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == cmd.User.Id);
|
||||||
if (lodestoneAuth != null && _botServices.DiscordLodestoneMapping.ContainsKey(cmd.User.Id))
|
if (lodestoneAuth != null && _botServices.DiscordLodestoneMapping.ContainsKey(cmd.User.Id))
|
||||||
{
|
{
|
||||||
var randomServer = _botServices.LodestoneServers[_botServices.Random.Next(_botServices.LodestoneServers.Length)];
|
var randomServer = _botServices.LodestoneServers[random.Next(_botServices.LodestoneServers.Length)];
|
||||||
var response = await req.GetAsync($"https://{randomServer}.finalfantasyxiv.com/lodestone/character/{_botServices.DiscordLodestoneMapping[cmd.User.Id]}").ConfigureAwait(false);
|
var response = await req.GetAsync($"https://{randomServer}.finalfantasyxiv.com/lodestone/character/{_botServices.DiscordLodestoneMapping[cmd.User.Id]}").ConfigureAwait(false);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -757,11 +850,7 @@ public class MareModule : InteractionModuleBase
|
|||||||
user.IsAdmin = true;
|
user.IsAdmin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_botServices.Configuration.PurgeUnusedAccounts)
|
user.LastLoggedIn = DateTime.UtcNow;
|
||||||
{
|
|
||||||
var purgedDays = _botServices.Configuration.PurgeUnusedAccountsPeriodInDays;
|
|
||||||
user.LastLoggedIn = DateTime.UtcNow - TimeSpan.FromDays(purgedDays) + TimeSpan.FromDays(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString());
|
var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString());
|
||||||
var auth = new Auth()
|
var auth = new Auth()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
using MareSynchronosServices;
|
using MareSynchronosServices;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
@@ -24,10 +19,13 @@ public class Program
|
|||||||
|
|
||||||
metrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, dbContext.Users.Count());
|
metrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, dbContext.Users.Count());
|
||||||
|
|
||||||
var options = host.Services.GetService<IOptions<ServicesConfiguration>>();
|
var options = host.Services.GetService<IConfigurationService<ServicesConfiguration>>();
|
||||||
|
var optionsServer = host.Services.GetService<IConfigurationService<ServerConfiguration>>();
|
||||||
var logger = host.Services.GetService<ILogger<Program>>();
|
var logger = host.Services.GetService<ILogger<Program>>();
|
||||||
logger.LogInformation("Loaded MareSynchronos Services Configuration");
|
logger.LogInformation("Loaded MareSynchronos Services Configuration (IsMain: {isMain})", options.IsMain);
|
||||||
logger.LogInformation(options.Value.ToString());
|
logger.LogInformation(options.ToString());
|
||||||
|
logger.LogInformation("Loaded MareSynchronos Server Configuration (IsMain: {isMain})", optionsServer.IsMain);
|
||||||
|
logger.LogInformation(optionsServer.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
host.Run();
|
host.Run();
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
using MareSynchronosShared.Utils;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MareSynchronosServices;
|
|
||||||
|
|
||||||
public class ServicesConfiguration : MareConfigurationBase
|
|
||||||
{
|
|
||||||
public string DiscordBotToken { get; set; } = string.Empty;
|
|
||||||
public bool PurgeUnusedAccounts { get; set; } = false;
|
|
||||||
public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14;
|
|
||||||
public int MaxExistingGroupsByUser { get; set; } = 3;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new();
|
|
||||||
sb.AppendLine(base.ToString());
|
|
||||||
sb.AppendLine($"{nameof(DiscordBotToken)} => {DiscordBotToken}");
|
|
||||||
sb.AppendLine($"{nameof(PurgeUnusedAccounts)} => {PurgeUnusedAccounts}");
|
|
||||||
sb.AppendLine($"{nameof(PurgeUnusedAccountsPeriodInDays)} => {PurgeUnusedAccountsPeriodInDays}");
|
|
||||||
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
using MareSynchronosServices.Discord;
|
using MareSynchronosServices.Discord;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MareSynchronosServices.Identity;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
|
using Grpc.Net.Client.Configuration;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
|
||||||
namespace MareSynchronosServices;
|
namespace MareSynchronosServices;
|
||||||
|
|
||||||
@@ -25,6 +21,8 @@ public class Startup
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
var mareConfig = Configuration.GetSection("MareSynchronos");
|
||||||
|
|
||||||
services.AddDbContextPool<MareDbContext>(options =>
|
services.AddDbContextPool<MareDbContext>(options =>
|
||||||
{
|
{
|
||||||
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
||||||
@@ -34,32 +32,58 @@ public class Startup
|
|||||||
options.EnableThreadSafetyChecks(false);
|
options.EnableThreadSafetyChecks(false);
|
||||||
}, Configuration.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024));
|
}, Configuration.GetValue(nameof(MareConfigurationBase.DbContextPoolSize), 1024));
|
||||||
|
|
||||||
services.AddSingleton(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string> {
|
services.AddSingleton(m => new MareMetrics(m.GetService<ILogger<MareMetrics>>(), new List<string> { },
|
||||||
}, new List<string>
|
new List<string>
|
||||||
{
|
{
|
||||||
MetricsAPI.GaugeUsersRegistered
|
MetricsAPI.GaugeUsersRegistered
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
var noRetryConfig = new MethodConfig
|
||||||
|
{
|
||||||
|
Names = { MethodName.Default },
|
||||||
|
RetryPolicy = null
|
||||||
|
};
|
||||||
|
|
||||||
|
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServicesConfiguration.MainServerGrpcAddress)));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
|
||||||
|
c.HttpHandler = new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
EnableMultipleHttp2Connections = true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddGrpcClient<ConfigurationService.ConfigurationServiceClient>(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri(mareConfig.GetValue<string>(nameof(ServicesConfiguration.MainServerGrpcAddress)));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { noRetryConfig } };
|
||||||
|
c.HttpHandler = new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
EnableMultipleHttp2Connections = true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
services.Configure<ServicesConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
|
services.Configure<ServicesConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
|
||||||
|
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
|
||||||
|
services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos"));
|
||||||
services.AddSingleton(Configuration);
|
services.AddSingleton(Configuration);
|
||||||
services.AddSingleton<DiscordBotServices>();
|
services.AddSingleton<DiscordBotServices>();
|
||||||
services.AddSingleton<IdentityHandler>();
|
|
||||||
services.AddSingleton<CleanupService>();
|
|
||||||
services.AddHostedService(provider => provider.GetService<CleanupService>());
|
|
||||||
services.AddHostedService<DiscordBot>();
|
services.AddHostedService<DiscordBot>();
|
||||||
services.AddGrpc();
|
services.AddSingleton<IConfigurationService<ServicesConfiguration>, MareConfigurationServiceServer<ServicesConfiguration>>();
|
||||||
|
services.AddSingleton<IConfigurationService<ServerConfiguration>, MareConfigurationServiceClient<ServerConfiguration>>();
|
||||||
|
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>, MareConfigurationServiceClient<MareConfigurationAuthBase>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
app.UseRouting();
|
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>();
|
||||||
|
|
||||||
var metricServer = new KestrelMetricServer(4982);
|
var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4982));
|
||||||
metricServer.Start();
|
metricServer.Start();
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
|
||||||
{
|
|
||||||
endpoints.MapGrpcService<IdentityService>();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using MareSynchronosServer;
|
|
||||||
using MareSynchronosShared.Data;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -13,7 +12,6 @@ public class SecretKeyAuthenticationHandler : AuthenticationHandler<Authenticati
|
|||||||
{
|
{
|
||||||
public const string AuthScheme = "SecretKeyGrpcAuth";
|
public const string AuthScheme = "SecretKeyGrpcAuth";
|
||||||
|
|
||||||
private readonly MareDbContext _mareDbContext;
|
|
||||||
private readonly IHttpContextAccessor _accessor;
|
private readonly IHttpContextAccessor _accessor;
|
||||||
private readonly SecretKeyAuthenticatorService secretKeyAuthenticatorService;
|
private readonly SecretKeyAuthenticatorService secretKeyAuthenticatorService;
|
||||||
|
|
||||||
@@ -26,6 +24,12 @@ public class SecretKeyAuthenticationHandler : AuthenticationHandler<Authenticati
|
|||||||
|
|
||||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
|
var endpoint = Context.GetEndpoint();
|
||||||
|
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.NoResult();
|
||||||
|
}
|
||||||
|
|
||||||
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
|
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
|
||||||
{
|
{
|
||||||
authHeader = string.Empty;
|
authHeader = string.Empty;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -13,25 +14,15 @@ public class SecretKeyAuthenticatorService
|
|||||||
{
|
{
|
||||||
private readonly MareMetrics _metrics;
|
private readonly MareMetrics _metrics;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly IConfigurationService<MareConfigurationAuthBase> _configurationService;
|
||||||
private readonly ILogger<SecretKeyAuthenticatorService> _logger;
|
private readonly ILogger<SecretKeyAuthenticatorService> _logger;
|
||||||
private readonly ConcurrentDictionary<string, SecretKeyAuthReply> _cachedPositiveResponses = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, SecretKeyAuthReply> _cachedPositiveResponses = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization?> _failedAuthorizations = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, SecretKeyFailedAuthorization?> _failedAuthorizations = new(StringComparer.Ordinal);
|
||||||
private readonly int _failedAttemptsForTempBan;
|
|
||||||
private readonly int _tempBanMinutes;
|
|
||||||
private readonly List<string> _whitelistedIps;
|
|
||||||
|
|
||||||
public SecretKeyAuthenticatorService(MareMetrics metrics, IServiceScopeFactory serviceScopeFactory, IOptions<MareConfigurationAuthBase> configuration, ILogger<SecretKeyAuthenticatorService> logger)
|
public SecretKeyAuthenticatorService(MareMetrics metrics, IServiceScopeFactory serviceScopeFactory, IConfigurationService<MareConfigurationAuthBase> configuration, ILogger<SecretKeyAuthenticatorService> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
var config = configuration.Value;
|
_configurationService = configuration;
|
||||||
_failedAttemptsForTempBan = config.FailedAuthForTempBan;
|
|
||||||
_tempBanMinutes = config.TempBanDurationInMinutes;
|
|
||||||
_whitelistedIps = config.WhitelistedIps;
|
|
||||||
foreach (var ip in _whitelistedIps)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Whitelisted IP: " + ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
_metrics = metrics;
|
_metrics = metrics;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
}
|
}
|
||||||
@@ -46,7 +37,8 @@ public class SecretKeyAuthenticatorService
|
|||||||
return cachedPositiveResponse;
|
return cachedPositiveResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization) && existingFailedAuthorization.FailedAttempts > _failedAttemptsForTempBan)
|
if (_failedAuthorizations.TryGetValue(ip, out var existingFailedAuthorization)
|
||||||
|
&& existingFailedAuthorization.FailedAttempts > _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.FailedAuthForTempBan), 5))
|
||||||
{
|
{
|
||||||
if (existingFailedAuthorization.ResetTask == null)
|
if (existingFailedAuthorization.ResetTask == null)
|
||||||
{
|
{
|
||||||
@@ -54,7 +46,7 @@ public class SecretKeyAuthenticatorService
|
|||||||
|
|
||||||
existingFailedAuthorization.ResetTask = Task.Run(async () =>
|
existingFailedAuthorization.ResetTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(_tempBanMinutes)).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromMinutes(_configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.TempBanDurationInMinutes), 5))).ConfigureAwait(false);
|
||||||
|
|
||||||
}).ContinueWith((t) =>
|
}).ContinueWith((t) =>
|
||||||
{
|
{
|
||||||
@@ -96,7 +88,8 @@ public class SecretKeyAuthenticatorService
|
|||||||
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
_metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures);
|
||||||
|
|
||||||
_logger.LogWarning("Failed authorization from {ip}", ip);
|
_logger.LogWarning("Failed authorization from {ip}", ip);
|
||||||
if (!_whitelistedIps.Any(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase)))
|
var whitelisted = _configurationService.GetValueOrDefault(nameof(MareConfigurationAuthBase.WhitelistedIps), new List<string>());
|
||||||
|
if (!whitelisted.Any(w => ip.Contains(w, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
if (_failedAuthorizations.TryGetValue(ip, out var auth))
|
if (_failedAuthorizations.TryGetValue(ip, out var auth))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace MareSynchronosServer;
|
namespace MareSynchronosShared;
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ service IdentificationService {
|
|||||||
rpc ReceiveStreamIdentStatusChange (ServerMessage) returns (stream IdentChange);
|
rpc ReceiveStreamIdentStatusChange (ServerMessage) returns (stream IdentChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service ConfigurationService {
|
||||||
|
rpc GetConfigurationEntry (KeyMessage) returns (ValueMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
message KeyMessage {
|
||||||
|
string key = 1;
|
||||||
|
string default = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ValueMessage {
|
||||||
|
string value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Empty { }
|
message Empty { }
|
||||||
|
|
||||||
message MultiUidMessage {
|
message MultiUidMessage {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public class GrpcConfigurationService<T> : ConfigurationService.ConfigurationServiceBase where T : class, IMareConfiguration
|
||||||
|
{
|
||||||
|
private readonly T _config;
|
||||||
|
private readonly ILogger<GrpcConfigurationService<T>> logger;
|
||||||
|
|
||||||
|
public GrpcConfigurationService(IOptions<T> config, ILogger<GrpcConfigurationService<T>> logger)
|
||||||
|
{
|
||||||
|
_config = config.Value;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
public override Task<ValueMessage> GetConfigurationEntry(KeyMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Remote requested {key}", request.Key);
|
||||||
|
var returnVal = _config.SerializeValue(request.Key, request.Default);
|
||||||
|
return Task.FromResult(new ValueMessage() { Value = returnVal });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
public interface IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
|
{
|
||||||
|
bool IsMain { get; }
|
||||||
|
T1 GetValue<T1>(string key);
|
||||||
|
T1 GetValueOrDefault<T1>(string key, T1 defaultValue);
|
||||||
|
string ToString();
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using Grpc.Net.ClientFactory;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using static MareSynchronosShared.Protos.ConfigurationService;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
public class MareConfigurationServiceClient<T> : IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
|
{
|
||||||
|
internal record RemoteCachedEntry(object Value, DateTime Inserted);
|
||||||
|
|
||||||
|
private readonly T _config;
|
||||||
|
private readonly ConcurrentDictionary<string, RemoteCachedEntry> _cachedRemoteProperties = new(StringComparer.Ordinal);
|
||||||
|
private readonly ILogger<MareConfigurationServiceClient<T>> _logger;
|
||||||
|
private readonly ConfigurationServiceClient _configurationServiceClient;
|
||||||
|
|
||||||
|
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, ConfigurationServiceClient configurationServiceClient)
|
||||||
|
{
|
||||||
|
_config = config.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_configurationServiceClient = configurationServiceClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, GrpcClientFactory grpcClientFactory, string grpcClientName)
|
||||||
|
{
|
||||||
|
_config = config.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_configurationServiceClient = grpcClientFactory.CreateClient<ConfigurationServiceClient>(grpcClientName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMain => false;
|
||||||
|
|
||||||
|
public T1 GetValueOrDefault<T1>(string key, T1 defaultValue)
|
||||||
|
{
|
||||||
|
var prop = _config.GetType().GetProperty(key);
|
||||||
|
if (prop == null) return defaultValue;
|
||||||
|
if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}");
|
||||||
|
bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any();
|
||||||
|
if (isRemote)
|
||||||
|
{
|
||||||
|
bool isCurrent = false;
|
||||||
|
if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry) && existingEntry.Inserted > DateTime.Now - TimeSpan.FromMinutes(30))
|
||||||
|
{
|
||||||
|
isCurrent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrent)
|
||||||
|
{
|
||||||
|
var result = GetValueFromGrpc(key, defaultValue, prop.PropertyType);
|
||||||
|
if (result == null) return defaultValue;
|
||||||
|
_cachedRemoteProperties[key] = result;
|
||||||
|
return (T1)_cachedRemoteProperties[key].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = prop.GetValue(_config);
|
||||||
|
return (T1)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteCachedEntry? GetValueFromGrpc(string key, object defaultValue, Type t)
|
||||||
|
{
|
||||||
|
// grab stuff from grpc
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Getting {key} from Grpc", key);
|
||||||
|
var response = _configurationServiceClient.GetConfigurationEntry(new KeyMessage { Key = key, Default = Convert.ToString(defaultValue, CultureInfo.InvariantCulture) });
|
||||||
|
_logger.LogInformation("Grpc Response for {key} = {value}", key, response.Value);
|
||||||
|
return new RemoteCachedEntry(JsonSerializer.Deserialize(response.Value, t), DateTime.Now);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T1 GetValue<T1>(string key)
|
||||||
|
{
|
||||||
|
var prop = _config.GetType().GetProperty(key);
|
||||||
|
if (prop == null) throw new KeyNotFoundException(key);
|
||||||
|
if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}");
|
||||||
|
bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any();
|
||||||
|
if (isRemote)
|
||||||
|
{
|
||||||
|
bool isCurrent = false;
|
||||||
|
if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry) && existingEntry.Inserted > DateTime.Now - TimeSpan.FromMinutes(30))
|
||||||
|
{
|
||||||
|
isCurrent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrent)
|
||||||
|
{
|
||||||
|
var result = GetValueFromGrpc(key, null, prop.PropertyType);
|
||||||
|
if (result == null) throw new KeyNotFoundException(key);
|
||||||
|
_cachedRemoteProperties[key] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_cachedRemoteProperties.ContainsKey(key)) throw new KeyNotFoundException(key);
|
||||||
|
|
||||||
|
return (T1)_cachedRemoteProperties[key].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = prop.GetValue(_config);
|
||||||
|
return (T1)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var props = _config.GetType().GetProperties();
|
||||||
|
StringBuilder sb = new();
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any();
|
||||||
|
var mi = GetType().GetMethod(nameof(GetValue)).MakeGenericMethod(prop.PropertyType);
|
||||||
|
var val = mi.Invoke(this, new[] { prop.Name });
|
||||||
|
var value = isRemote ? val : prop.GetValue(_config);
|
||||||
|
sb.AppendLine($"{prop.Name} (IsRemote: {isRemote}) => {value}");
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
|
public class MareConfigurationServiceServer<T> : IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
|
{
|
||||||
|
private readonly T _config;
|
||||||
|
public bool IsMain => true;
|
||||||
|
|
||||||
|
public MareConfigurationServiceServer(IOptions<T> config)
|
||||||
|
{
|
||||||
|
_config = config.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T1 GetValueOrDefault<T1>(string key, T1 defaultValue)
|
||||||
|
{
|
||||||
|
return _config.GetValueOrDefault<T1>(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T1 GetValue<T1>(string key)
|
||||||
|
{
|
||||||
|
return _config.GetValue<T1>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var props = _config.GetType().GetProperties();
|
||||||
|
StringBuilder sb = new();
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{prop.Name} (IsRemote: {prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()}) => {prop.GetValue(_config)}");
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
public interface IMareConfiguration
|
||||||
|
{
|
||||||
|
T GetValueOrDefault<T>(string key, T defaultValue);
|
||||||
|
T GetValue<T>(string key);
|
||||||
|
string SerializeValue(string key, string defaultValue);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
public class MareConfigurationAuthBase : MareConfigurationBase
|
||||||
|
{
|
||||||
|
public Uri MainServerGrpcAddress { get; set; } = null;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public int FailedAuthForTempBan { get; set; } = 5;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public int TempBanDurationInMinutes { get; set; } = 5;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public List<string> WhitelistedIps { get; set; } = new();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new();
|
||||||
|
sb.AppendLine(base.ToString());
|
||||||
|
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
||||||
|
sb.AppendLine($"{nameof(FailedAuthForTempBan)} => {FailedAuthForTempBan}");
|
||||||
|
sb.AppendLine($"{nameof(TempBanDurationInMinutes)} => {TempBanDurationInMinutes}");
|
||||||
|
sb.AppendLine($"{nameof(WhitelistedIps)} => {string.Join(", ", WhitelistedIps)}");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,46 @@
|
|||||||
using System.Text;
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Utils;
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
public class MareConfigurationBase
|
public class MareConfigurationBase : IMareConfiguration
|
||||||
{
|
{
|
||||||
public int DbContextPoolSize { get; set; } = 100;
|
public int DbContextPoolSize { get; set; } = 100;
|
||||||
|
public string ShardName { get; set; } = string.Empty;
|
||||||
|
public int MetricsPort { get; set; } = 4981;
|
||||||
|
|
||||||
|
public T GetValue<T>(string key)
|
||||||
|
{
|
||||||
|
var prop = GetType().GetProperty(key);
|
||||||
|
if (prop == null) throw new KeyNotFoundException(key);
|
||||||
|
if (prop.PropertyType != typeof(T)) throw new ArgumentException($"Requested {key} with T:{typeof(T)}, where {key} is {prop.PropertyType}");
|
||||||
|
return (T)prop.GetValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetValueOrDefault<T>(string key, T defaultValue)
|
||||||
|
{
|
||||||
|
var prop = GetType().GetProperty(key);
|
||||||
|
if (prop.PropertyType != typeof(T)) throw new ArgumentException($"Requested {key} with T:{typeof(T)}, where {key} is {prop.PropertyType}");
|
||||||
|
if (prop == null) return defaultValue;
|
||||||
|
return (T)prop.GetValue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SerializeValue(string key, string defaultValue)
|
||||||
|
{
|
||||||
|
var prop = GetType().GetProperty(key);
|
||||||
|
if (prop == null) return defaultValue;
|
||||||
|
if (prop.GetCustomAttribute<RemoteConfigurationAttribute>() == null) return defaultValue;
|
||||||
|
return JsonSerializer.Serialize(prop.GetValue(this), prop.PropertyType);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
|
sb.AppendLine(base.ToString());
|
||||||
|
sb.AppendLine($"{nameof(ShardName)} => {ShardName}");
|
||||||
sb.AppendLine($"{nameof(DbContextPoolSize)} => {DbContextPoolSize}");
|
sb.AppendLine($"{nameof(DbContextPoolSize)} => {DbContextPoolSize}");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MareConfigurationAuthBase : MareConfigurationBase
|
|
||||||
{
|
|
||||||
public int DbContextPoolSize { get; set; } = 100;
|
|
||||||
public int FailedAuthForTempBan { get; set; } = 5;
|
|
||||||
public int TempBanDurationInMinutes { get; set; } = 5;
|
|
||||||
public List<string> WhitelistedIps { get; set; } = new();
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new();
|
|
||||||
sb.AppendLine($"{nameof(FailedAuthForTempBan)} => {FailedAuthForTempBan}");
|
|
||||||
sb.AppendLine($"{nameof(TempBanDurationInMinutes)} => {TempBanDurationInMinutes}");
|
|
||||||
sb.AppendLine($"{nameof(WhitelistedIps)} => {string.Join(", ", WhitelistedIps)}");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class RemoteConfigurationAttribute : Attribute { }
|
||||||
@@ -1,32 +1,39 @@
|
|||||||
using MareSynchronosShared.Utils;
|
using System.Text;
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MareSynchronosServer;
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
public class ServerConfiguration : MareConfigurationAuthBase
|
public class ServerConfiguration : MareConfigurationAuthBase
|
||||||
{
|
{
|
||||||
public Uri CdnFullUrl { get; set; } = null;
|
|
||||||
public Uri ServiceAddress { get; set; } = null;
|
|
||||||
public Uri StaticFileServiceAddress { get; set; } = null;
|
|
||||||
public string RedisConnectionString { get; set; } = string.Empty;
|
public string RedisConnectionString { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public Uri CdnFullUrl { get; set; } = null;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public Uri StaticFileServiceAddress { get; set; } = null;
|
||||||
|
[RemoteConfiguration]
|
||||||
public int MaxExistingGroupsByUser { get; set; } = 3;
|
public int MaxExistingGroupsByUser { get; set; } = 3;
|
||||||
|
[RemoteConfiguration]
|
||||||
public int MaxJoinedGroupsByUser { get; set; } = 6;
|
public int MaxJoinedGroupsByUser { get; set; } = 6;
|
||||||
|
[RemoteConfiguration]
|
||||||
public int MaxGroupUserCount { get; set; } = 100;
|
public int MaxGroupUserCount { get; set; } = 100;
|
||||||
public string ShardName { get; set; } = string.Empty;
|
[RemoteConfiguration]
|
||||||
|
public bool PurgeUnusedAccounts { get; set; } = false;
|
||||||
|
[RemoteConfiguration]
|
||||||
|
public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14;
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine(base.ToString());
|
sb.AppendLine(base.ToString());
|
||||||
sb.AppendLine($"{nameof(ShardName)} => {ShardName}");
|
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
||||||
sb.AppendLine($"{nameof(CdnFullUrl)} => {CdnFullUrl}");
|
sb.AppendLine($"{nameof(CdnFullUrl)} => {CdnFullUrl}");
|
||||||
sb.AppendLine($"{nameof(ServiceAddress)} => {ServiceAddress}");
|
|
||||||
sb.AppendLine($"{nameof(StaticFileServiceAddress)} => {StaticFileServiceAddress}");
|
sb.AppendLine($"{nameof(StaticFileServiceAddress)} => {StaticFileServiceAddress}");
|
||||||
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
||||||
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
||||||
sb.AppendLine($"{nameof(MaxJoinedGroupsByUser)} => {MaxJoinedGroupsByUser}");
|
sb.AppendLine($"{nameof(MaxJoinedGroupsByUser)} => {MaxJoinedGroupsByUser}");
|
||||||
sb.AppendLine($"{nameof(MaxGroupUserCount)} => {MaxGroupUserCount}");
|
sb.AppendLine($"{nameof(MaxGroupUserCount)} => {MaxGroupUserCount}");
|
||||||
|
sb.AppendLine($"{nameof(PurgeUnusedAccounts)} => {PurgeUnusedAccounts}");
|
||||||
|
sb.AppendLine($"{nameof(PurgeUnusedAccountsPeriodInDays)} => {PurgeUnusedAccountsPeriodInDays}");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
|
public class ServicesConfiguration : MareConfigurationBase
|
||||||
|
{
|
||||||
|
public string DiscordBotToken { get; set; } = string.Empty;
|
||||||
|
public Uri MainServerGrpcAddress { get; set; } = null;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new();
|
||||||
|
sb.AppendLine(base.ToString());
|
||||||
|
sb.AppendLine($"{nameof(DiscordBotToken)} => {DiscordBotToken}");
|
||||||
|
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,68 @@
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Utils;
|
namespace MareSynchronosShared.Utils;
|
||||||
|
|
||||||
public static class SharedDbFunctions
|
public static class SharedDbFunctions
|
||||||
{
|
{
|
||||||
|
public static async Task PurgeUser(ILogger _logger, User user, MareDbContext dbContext, int maxGroupsByUser)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Purging user: {uid}", user.UID);
|
||||||
|
|
||||||
|
var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID);
|
||||||
|
|
||||||
|
if (lodestone != null)
|
||||||
|
{
|
||||||
|
dbContext.Remove(lodestone);
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
|
||||||
|
|
||||||
|
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();
|
||||||
|
dbContext.Files.RemoveRange(userFiles);
|
||||||
|
|
||||||
|
var ownPairData = dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToList();
|
||||||
|
dbContext.ClientPairs.RemoveRange(ownPairData);
|
||||||
|
var otherPairData = dbContext.ClientPairs.Include(u => u.User)
|
||||||
|
.Where(u => u.OtherUser.UID == user.UID).ToList();
|
||||||
|
dbContext.ClientPairs.RemoveRange(otherPairData);
|
||||||
|
|
||||||
|
var userJoinedGroups = await dbContext.GroupPairs.Include(g => g.Group).Where(u => u.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var userGroupPair in userJoinedGroups)
|
||||||
|
{
|
||||||
|
bool ownerHasLeft = string.Equals(userGroupPair.Group.OwnerUID, user.UID, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (ownerHasLeft)
|
||||||
|
{
|
||||||
|
var groupPairs = await dbContext.GroupPairs.Where(g => g.GroupGID == userGroupPair.GroupGID && g.GroupUserUID != user.UID).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!groupPairs.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Group {gid} has no new owner, deleting", userGroupPair.GroupGID);
|
||||||
|
dbContext.Groups.Remove(userGroupPair.Group);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = await MigrateOrDeleteGroup(dbContext, userGroupPair.Group, groupPairs, maxGroupsByUser).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbContext.GroupPairs.Remove(userGroupPair);
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("User purged: {uid}", user.UID);
|
||||||
|
|
||||||
|
dbContext.Auth.Remove(auth);
|
||||||
|
dbContext.Users.Remove(user);
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<(bool, string)> MigrateOrDeleteGroup(MareDbContext context, Group group, List<GroupPair> groupPairs, int maxGroupsByUser)
|
public static async Task<(bool, string)> MigrateOrDeleteGroup(MareDbContext context, Group group, List<GroupPair> groupPairs, int maxGroupsByUser)
|
||||||
{
|
{
|
||||||
bool groupHasMigrated = false;
|
bool groupHasMigrated = false;
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ using System.Text;
|
|||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer;
|
namespace MareSynchronosStaticFilesServer;
|
||||||
|
|
||||||
public class StaticFilesServerConfiguration : MareConfigurationAuthBase
|
public class StaticFilesServerConfiguration : MareConfigurationBase
|
||||||
{
|
{
|
||||||
|
public Uri FileServerGrpcAddress { get; set; } = null;
|
||||||
public int ForcedDeletionOfFilesAfterHours { get; set; } = -1;
|
public int ForcedDeletionOfFilesAfterHours { get; set; } = -1;
|
||||||
public double CacheSizeHardLimitInGiB { get; set; } = -1;
|
public double CacheSizeHardLimitInGiB { get; set; } = -1;
|
||||||
public int UnusedFileRetentionPeriodInDays { get; set; } = -1;
|
public int UnusedFileRetentionPeriodInDays { get; set; } = 14;
|
||||||
public string CacheDirectory { get; set; }
|
public string CacheDirectory { get; set; }
|
||||||
public Uri? RemoteCacheSourceUri { get; set; } = null;
|
public Uri? RemoteCacheSourceUri { get; set; } = null;
|
||||||
|
public Uri MainServerGrpcAddress { get; set; } = null;
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine(base.ToString());
|
sb.AppendLine(base.ToString());
|
||||||
|
sb.AppendLine($"{nameof(FileServerGrpcAddress)} => {FileServerGrpcAddress}");
|
||||||
|
sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}");
|
||||||
sb.AppendLine($"{nameof(ForcedDeletionOfFilesAfterHours)} => {ForcedDeletionOfFilesAfterHours}");
|
sb.AppendLine($"{nameof(ForcedDeletionOfFilesAfterHours)} => {ForcedDeletionOfFilesAfterHours}");
|
||||||
sb.AppendLine($"{nameof(CacheSizeHardLimitInGiB)} => {CacheSizeHardLimitInGiB}");
|
sb.AppendLine($"{nameof(CacheSizeHardLimitInGiB)} => {CacheSizeHardLimitInGiB}");
|
||||||
sb.AppendLine($"{nameof(UnusedFileRetentionPeriodInDays)} => {UnusedFileRetentionPeriodInDays}");
|
sb.AppendLine($"{nameof(UnusedFileRetentionPeriodInDays)} => {UnusedFileRetentionPeriodInDays}");
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
@@ -14,13 +15,13 @@ public class CachedFileProvider
|
|||||||
private readonly ConcurrentDictionary<string, Task> _currentTransfers = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, Task> _currentTransfers = new(StringComparer.Ordinal);
|
||||||
private bool IsMainServer => _remoteCacheSourceUri == null;
|
private bool IsMainServer => _remoteCacheSourceUri == null;
|
||||||
|
|
||||||
public CachedFileProvider(IOptions<StaticFilesServerConfiguration> configuration, ILogger<CachedFileProvider> logger, FileStatisticsService fileStatisticsService, MareMetrics metrics)
|
public CachedFileProvider(IConfigurationService<StaticFilesServerConfiguration> configuration, ILogger<CachedFileProvider> logger, FileStatisticsService fileStatisticsService, MareMetrics metrics)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileStatisticsService = fileStatisticsService;
|
_fileStatisticsService = fileStatisticsService;
|
||||||
_metrics = metrics;
|
_metrics = metrics;
|
||||||
_remoteCacheSourceUri = configuration.Value.RemoteCacheSourceUri;
|
_remoteCacheSourceUri = configuration.GetValueOrDefault<Uri>(nameof(StaticFilesServerConfiguration.RemoteCacheSourceUri), null);
|
||||||
_basePath = configuration.Value.CacheDirectory;
|
_basePath = configuration.GetValue<string>(nameof(StaticFilesServerConfiguration.CacheDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FileStream?> GetFileStream(string hash, string auth)
|
public async Task<FileStream?> GetFileStream(string hash, string auth)
|
||||||
@@ -79,6 +80,21 @@ public class CachedFileProvider
|
|||||||
|
|
||||||
_fileStatisticsService.LogFile(hash, fi.Length);
|
_fileStatisticsService.LogFile(hash, fi.Length);
|
||||||
|
|
||||||
return new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
int attempts = 0;
|
||||||
|
while (attempts < 5)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
attempts++;
|
||||||
|
_logger.LogWarning(ex, "Error opening file, retrying");
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Could not open file " + fi.FullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer;
|
namespace MareSynchronosStaticFilesServer;
|
||||||
@@ -11,19 +12,19 @@ public class FileCleanupService : IHostedService
|
|||||||
private readonly MareMetrics _metrics;
|
private readonly MareMetrics _metrics;
|
||||||
private readonly ILogger<FileCleanupService> _logger;
|
private readonly ILogger<FileCleanupService> _logger;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly StaticFilesServerConfiguration _configuration;
|
private readonly IConfigurationService<StaticFilesServerConfiguration> _configuration;
|
||||||
private readonly bool _isMainServer;
|
private readonly bool _isMainServer;
|
||||||
private readonly string _cacheDir;
|
private readonly string _cacheDir;
|
||||||
private CancellationTokenSource _cleanupCts;
|
private CancellationTokenSource _cleanupCts;
|
||||||
|
|
||||||
public FileCleanupService(MareMetrics metrics, ILogger<FileCleanupService> logger, IServiceProvider services, IOptions<StaticFilesServerConfiguration> configuration)
|
public FileCleanupService(MareMetrics metrics, ILogger<FileCleanupService> logger, IServiceProvider services, IConfigurationService<StaticFilesServerConfiguration> configuration)
|
||||||
{
|
{
|
||||||
_metrics = metrics;
|
_metrics = metrics;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_services = services;
|
_services = services;
|
||||||
_configuration = configuration.Value;
|
_configuration = configuration;
|
||||||
_isMainServer = _configuration.RemoteCacheSourceUri == null;
|
_isMainServer = configuration.IsMain;
|
||||||
_cacheDir = _configuration.CacheDirectory;
|
_cacheDir = _configuration.GetValue<string>(nameof(StaticFilesServerConfiguration.CacheDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
@@ -60,26 +61,32 @@ public class FileCleanupService : IHostedService
|
|||||||
await dbContext.SaveChangesAsync(ct).ConfigureAwait(false);
|
await dbContext.SaveChangesAsync(ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("File Cleanup Complete, next run at {date}", DateTime.Now.Add(TimeSpan.FromMinutes(10)));
|
var now = DateTime.Now;
|
||||||
await Task.Delay(TimeSpan.FromMinutes(10), ct).ConfigureAwait(false);
|
TimeOnly currentTime = new(now.Hour, now.Minute, now.Second);
|
||||||
|
TimeOnly futureTime = new(now.Hour, now.Minute - now.Minute % 10, 0);
|
||||||
|
var span = futureTime.AddMinutes(10) - currentTime;
|
||||||
|
|
||||||
|
_logger.LogInformation("File Cleanup Complete, next run at {date}", now.Add(span));
|
||||||
|
await Task.Delay(span, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanUpFilesBeyondSizeLimit(MareDbContext dbContext, CancellationToken ct)
|
private void CleanUpFilesBeyondSizeLimit(MareDbContext dbContext, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (_configuration.CacheSizeHardLimitInGiB <= 0)
|
var sizeLimit = _configuration.GetValueOrDefault<double>(nameof(StaticFilesServerConfiguration.CacheSizeHardLimitInGiB), -1);
|
||||||
|
if (sizeLimit <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Cleaning up files beyond the cache size limit of {cacheSizeLimit} GiB", _configuration.CacheSizeHardLimitInGiB);
|
_logger.LogInformation("Cleaning up files beyond the cache size limit of {cacheSizeLimit} GiB", sizeLimit);
|
||||||
var allLocalFiles = Directory.EnumerateFiles(_cacheDir, "*", SearchOption.AllDirectories)
|
var allLocalFiles = Directory.EnumerateFiles(_cacheDir, "*", SearchOption.AllDirectories)
|
||||||
.Select(f => new FileInfo(f)).ToList()
|
.Select(f => new FileInfo(f)).ToList()
|
||||||
.OrderBy(f => f.LastAccessTimeUtc).ToList();
|
.OrderBy(f => f.LastAccessTimeUtc).ToList();
|
||||||
var totalCacheSizeInBytes = allLocalFiles.Sum(s => s.Length);
|
var totalCacheSizeInBytes = allLocalFiles.Sum(s => s.Length);
|
||||||
long cacheSizeLimitInBytes = (long)ByteSize.FromGibiBytes(_configuration.CacheSizeHardLimitInGiB).Bytes;
|
long cacheSizeLimitInBytes = (long)ByteSize.FromGibiBytes(sizeLimit).Bytes;
|
||||||
while (totalCacheSizeInBytes > cacheSizeLimitInBytes && allLocalFiles.Any() && !ct.IsCancellationRequested)
|
while (totalCacheSizeInBytes > cacheSizeLimitInBytes && allLocalFiles.Any() && !ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var oldestFile = allLocalFiles[0];
|
var oldestFile = allLocalFiles[0];
|
||||||
@@ -106,15 +113,18 @@ public class FileCleanupService : IHostedService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Cleaning up files older than {filesOlderThanDays} days", _configuration.UnusedFileRetentionPeriodInDays);
|
var unusedRetention = _configuration.GetValueOrDefault<int>(nameof(StaticFilesServerConfiguration.UnusedFileRetentionPeriodInDays), 14);
|
||||||
if (_configuration.ForcedDeletionOfFilesAfterHours > 0)
|
var forcedDeletionAfterHours = _configuration.GetValueOrDefault<int>(nameof(StaticFilesServerConfiguration.ForcedDeletionOfFilesAfterHours), -1);
|
||||||
|
|
||||||
|
_logger.LogInformation("Cleaning up files older than {filesOlderThanDays} days", unusedRetention);
|
||||||
|
if (forcedDeletionAfterHours > 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Cleaning up files written to longer than {hours}h ago", _configuration.ForcedDeletionOfFilesAfterHours);
|
_logger.LogInformation("Cleaning up files written to longer than {hours}h ago", forcedDeletionAfterHours);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up files in DB but not on disk or last access is expired
|
// clean up files in DB but not on disk or last access is expired
|
||||||
var prevTime = DateTime.Now.Subtract(TimeSpan.FromDays(_configuration.UnusedFileRetentionPeriodInDays));
|
var prevTime = DateTime.Now.Subtract(TimeSpan.FromDays(unusedRetention));
|
||||||
var prevTimeForcedDeletion = DateTime.Now.Subtract(TimeSpan.FromHours(_configuration.ForcedDeletionOfFilesAfterHours));
|
var prevTimeForcedDeletion = DateTime.Now.Subtract(TimeSpan.FromHours(forcedDeletionAfterHours));
|
||||||
var allFiles = dbContext.Files.ToList();
|
var allFiles = dbContext.Files.ToList();
|
||||||
foreach (var fileCache in allFiles.Where(f => f.Uploaded))
|
foreach (var fileCache in allFiles.Where(f => f.Uploaded))
|
||||||
{
|
{
|
||||||
@@ -133,7 +143,7 @@ public class FileCleanupService : IHostedService
|
|||||||
if (_isMainServer)
|
if (_isMainServer)
|
||||||
dbContext.Files.Remove(fileCache);
|
dbContext.Files.Remove(fileCache);
|
||||||
}
|
}
|
||||||
else if (file != null && _configuration.ForcedDeletionOfFilesAfterHours > 0 && file.LastWriteTime < prevTimeForcedDeletion)
|
else if (file != null && forcedDeletionAfterHours > 0 && file.LastWriteTime < prevTimeForcedDeletion)
|
||||||
{
|
{
|
||||||
_metrics.DecGauge(MetricsAPI.GaugeFilesTotalSize, file.Length);
|
_metrics.DecGauge(MetricsAPI.GaugeFilesTotalSize, file.Length);
|
||||||
_metrics.DecGauge(MetricsAPI.GaugeFilesTotal);
|
_metrics.DecGauge(MetricsAPI.GaugeFilesTotal);
|
||||||
@@ -147,24 +157,7 @@ public class FileCleanupService : IHostedService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clean up files that are on disk but not in DB for some reason
|
// clean up files that are on disk but not in DB for some reason
|
||||||
if (_isMainServer)
|
CleanUpOrphanedFiles(allFiles, ct);
|
||||||
{
|
|
||||||
var allFilesHashes = new HashSet<string>(allFiles.Select(a => a.Hash.ToUpperInvariant()), StringComparer.Ordinal);
|
|
||||||
DirectoryInfo dir = new(_cacheDir);
|
|
||||||
var allFilesInDir = dir.GetFiles("*", SearchOption.AllDirectories);
|
|
||||||
foreach (var file in allFilesInDir)
|
|
||||||
{
|
|
||||||
if (!allFilesHashes.Contains(file.Name.ToUpperInvariant()))
|
|
||||||
{
|
|
||||||
_metrics.DecGauge(MetricsAPI.GaugeFilesTotalSize, file.Length);
|
|
||||||
_metrics.DecGauge(MetricsAPI.GaugeFilesTotal);
|
|
||||||
file.Delete();
|
|
||||||
_logger.LogInformation("File not in DB, deleting: {fileName}", file.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
ct.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -172,6 +165,28 @@ public class FileCleanupService : IHostedService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CleanUpOrphanedFiles(List<FileCache> allFiles, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (_isMainServer)
|
||||||
|
{
|
||||||
|
var allFilesHashes = new HashSet<string>(allFiles.Select(a => a.Hash.ToUpperInvariant()), StringComparer.Ordinal);
|
||||||
|
DirectoryInfo dir = new(_cacheDir);
|
||||||
|
var allFilesInDir = dir.GetFiles("*", SearchOption.AllDirectories);
|
||||||
|
foreach (var file in allFilesInDir)
|
||||||
|
{
|
||||||
|
if (!allFilesHashes.Contains(file.Name.ToUpperInvariant()))
|
||||||
|
{
|
||||||
|
_metrics.DecGauge(MetricsAPI.GaugeFilesTotalSize, file.Length);
|
||||||
|
_metrics.DecGauge(MetricsAPI.GaugeFilesTotal);
|
||||||
|
file.Delete();
|
||||||
|
_logger.LogInformation("File not in DB, deleting: {fileName}", file.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_cleanupCts.Cancel();
|
_cleanupCts.Cancel();
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ public class FileStatisticsService : IHostedService
|
|||||||
_pastHourFiles = new(StringComparer.Ordinal);
|
_pastHourFiles = new(StringComparer.Ordinal);
|
||||||
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastHour, 0);
|
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastHour, 0);
|
||||||
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastHourSize, 0);
|
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastHourSize, 0);
|
||||||
await Task.Delay(TimeSpan.FromHours(1), _resetCancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
TimeOnly currentTime = new(now.Hour, now.Minute, now.Second);
|
||||||
|
TimeOnly futureTime = new(now.Hour, 0, 0);
|
||||||
|
var span = futureTime.AddHours(1) - currentTime;
|
||||||
|
|
||||||
|
await Task.Delay(span, _resetCancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +70,13 @@ public class FileStatisticsService : IHostedService
|
|||||||
_pastDayFiles = new(StringComparer.Ordinal);
|
_pastDayFiles = new(StringComparer.Ordinal);
|
||||||
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastDay, 0);
|
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastDay, 0);
|
||||||
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastDaySize, 0);
|
_metrics.SetGaugeTo(MetricsAPI.GaugeFilesUniquePastDaySize, 0);
|
||||||
await Task.Delay(TimeSpan.FromDays(1), _resetCancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
TimeOnly currentTime = new(now.Hour, now.Minute, now.Second);
|
||||||
|
TimeOnly futureTime = new(0, 0, 0);
|
||||||
|
var span = futureTime - currentTime;
|
||||||
|
|
||||||
|
await Task.Delay(span, _resetCancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class FilesController : Controller
|
|||||||
[HttpGet("{fileId}")]
|
[HttpGet("{fileId}")]
|
||||||
public async Task<IActionResult> GetFile(string fileId)
|
public async Task<IActionResult> GetFile(string fileId)
|
||||||
{
|
{
|
||||||
var authedUser = HttpContext.User.Claims.FirstOrDefault(f => string.Equals(f.Type, ClaimTypes.NameIdentifier, System.StringComparison.Ordinal))?.Value ?? "Unknown";
|
var authedUser = HttpContext.User.Claims.FirstOrDefault(f => string.Equals(f.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||||
_logger.LogInformation($"GetFile:{authedUser}:{fileId}");
|
_logger.LogInformation($"GetFile:{authedUser}:{fileId}");
|
||||||
|
|
||||||
var fs = await _cachedFileProvider.GetFileStream(fileId, Request.Headers["Authorization"]);
|
var fs = await _cachedFileProvider.GetFileStream(fileId, Request.Headers["Authorization"]);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer;
|
namespace MareSynchronosStaticFilesServer;
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ public class GrpcFileService : FileService.FileServiceBase
|
|||||||
private readonly ILogger<GrpcFileService> _logger;
|
private readonly ILogger<GrpcFileService> _logger;
|
||||||
private readonly MareMetrics _metricsClient;
|
private readonly MareMetrics _metricsClient;
|
||||||
|
|
||||||
public GrpcFileService(MareDbContext mareDbContext, IOptions<StaticFilesServerConfiguration> configuration, ILogger<GrpcFileService> logger, MareMetrics metricsClient)
|
public GrpcFileService(MareDbContext mareDbContext, IConfigurationService<StaticFilesServerConfiguration> configuration, ILogger<GrpcFileService> logger, MareMetrics metricsClient)
|
||||||
{
|
{
|
||||||
_basePath = configuration.Value.CacheDirectory;
|
_basePath = configuration.GetValue<string>(nameof(StaticFilesServerConfiguration.CacheDirectory));
|
||||||
_mareDbContext = mareDbContext;
|
_mareDbContext = mareDbContext;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_metricsClient = metricsClient;
|
_metricsClient = metricsClient;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace MareSynchronosStaticFilesServer;
|
namespace MareSynchronosStaticFilesServer;
|
||||||
@@ -11,10 +13,13 @@ public class Program
|
|||||||
|
|
||||||
using (var scope = host.Services.CreateScope())
|
using (var scope = host.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var options = host.Services.GetService<IOptions<StaticFilesServerConfiguration>>();
|
var options = host.Services.GetService<IConfigurationService<StaticFilesServerConfiguration>>();
|
||||||
|
var optionsServer = host.Services.GetService<IConfigurationService<MareConfigurationAuthBase>>();
|
||||||
var logger = host.Services.GetService<ILogger<Program>>();
|
var logger = host.Services.GetService<ILogger<Program>>();
|
||||||
logger.LogInformation("Loaded MareSynchronos Static Files Server Configuration");
|
logger.LogInformation("Loaded MareSynchronos Static Files Server Configuration (IsMain: {isMain})", options.IsMain);
|
||||||
logger.LogInformation(options.Value.ToString());
|
logger.LogInformation(options.ToString());
|
||||||
|
logger.LogInformation("Loaded MareSynchronos Server Auth Configuration (IsMain: {isMain})", optionsServer.IsMain);
|
||||||
|
logger.LogInformation(optionsServer.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
host.Run();
|
host.Run();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user