diff --git a/README.md b/README.md index fd2a09a41c..eee581a72c 100644 --- a/README.md +++ b/README.md @@ -419,9 +419,15 @@ If TGS was installed via a package manager, using the TGS self updater will caus To avoid this, use the package manager to update TGS. It is just as seamless as the self-updater. +##### apt + +```sh +sudo apt update && sudo apt upgrade -y +``` + #### Notifications -If a server update is available, it will be indicated in the response from the GET /Administration endpoint. For more active notifications, you can subscribe to [this GitHub discussion](https://github.com/tgstation/tgstation-server/discussions/1322). +If a server update is available, it will be indicated in the response from the GET /Administration endpoint and shown as a green exclamation mark in the webpanel navbar. For more active notifications, you can subscribe to [this GitHub discussion](https://github.com/tgstation/tgstation-server/discussions/1322). ### Users diff --git a/build/package/deb/MakeInstall b/build/package/deb/MakeInstall index d3eb584ee6..982f06b80a 100755 --- a/build/package/deb/MakeInstall +++ b/build/package/deb/MakeInstall @@ -1,8 +1,14 @@ #!/usr/bin/make -f install: - mkdir -p $(DESTDIR)/opt/tgstation-server + install -d $(DESTDIR)/opt/tgstation-server cp -r artifacts/* $(DESTDIR)/opt/tgstation-server + install -d $(DESTDIR)/etc/tgstation-server + cp artifacts/appsettings.yml $(DESTDIR)/etc/tgstation-server/appsettings.ex.yml + echo -e "# tgstation-server configuration file\n# See /etc/tgstation-server/appsettings.ex.yml for details on individual configuration options" > $(DESTDIR)/etc/tgstation-server/appsettings.Production.yml + dh_link $(DESTDIR)/etc/tgstation-server/appsettings.Production.yml $(DESTDIR)/opt/tgstation-server/appsettings.Production.yml + install -d $(DESTDIR)/usr/bin + install build/package/deb/tgs-configure $(DESTDIR)/usr/bin/ uninstall: mkdir -p $(DESTDIR)/opt/tgstation-server diff --git a/build/package/deb/debian/control b/build/package/deb/debian/control index dac12b8594..aa670a2e2c 100644 --- a/build/package/deb/debian/control +++ b/build/package/deb/debian/control @@ -21,6 +21,7 @@ Depends: libstdc++6:i386 [amd64], libstdc++6 [i386], gcc-multilib [amd64], + libsystemd0, Recommends: gdb, Description: A production scale tool for BYOND server management diff --git a/build/package/deb/debian/postinst b/build/package/deb/debian/postinst index 933c850104..4f6d808f98 100755 --- a/build/package/deb/debian/postinst +++ b/build/package/deb/debian/postinst @@ -1,7 +1,21 @@ -#!/bin/bash +#!/bin/sh -e -pushd /opt/tgstation-server -dotnet /opt/tgstation-server/lib/Default/Tgstation.Server.Host.dll General:SetupWizardMode=Only -popd +if [[ "$1" = "configure" ]]; then + systemctl mask tgstation-server +fi #DEBHELPER# + +if [[ "$1" = "configure" ]]; then + echo " _ _ _ _ " + echo " | |_ __ _ ___| |_ __ _| |_(_) ___ _ __ ___ ___ _ ____ _____ _ __ " + echo " | __/ _` / __| __/ _` | __| |/ _ \| '_ \ _____/ __|/ _ \ '__\ \ / / _ \ '__|" + echo " | || (_| \__ \ || (_| | |_| | (_) | | | |_____\__ \ __/ | \ V / __/ | " + echo " \__\__, |___/\__\__,_|\__|_|\___/|_| |_| |___/\___|_| \_/ \___|_| " + echo " |___/ " + echo "tgstation-server is now installed but must first be configured" + echo "Run 'sudo tgs-configure' to interactively configure your server" + echo "Alternatively, edit '/etc/tgstation-server/appsettings.Production.yml' to your desired specifications" + echo "Once complete, run 'sudo systemctl start tgstation-server' to start the service" + echo "You should do this now to prevent the service from starting with invalid configuration on the next system reboot" +fi diff --git a/build/package/deb/debian/prerm b/build/package/deb/debian/prerm index 24938fbccf..759b0ed5fa 100755 --- a/build/package/deb/debian/prerm +++ b/build/package/deb/debian/prerm @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh -e #DEBHELPER# diff --git a/build/package/deb/debian/rules b/build/package/deb/debian/rules index 1c04da305c..d09702f2dd 100755 --- a/build/package/deb/debian/rules +++ b/build/package/deb/debian/rules @@ -25,4 +25,4 @@ override_dh_strip: override_dh_shlibdeps: override_dh_installsystemd: - dh_installsystemd -v --name=tgstation-server --restart-after-upgrade + dh_installsystemd -v --restart-after-upgrade diff --git a/build/package/deb/tgs-configure b/build/package/deb/tgs-configure new file mode 100755 index 0000000000..05546f2a63 --- /dev/null +++ b/build/package/deb/tgs-configure @@ -0,0 +1,5 @@ +#!/bin/sh + +pushd /opt/tgstation-server +dotnet /opt/tgstation-server/lib/Default/Tgstation.Server.Host.dll General:SetupWizardMode=Only +popd diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh index d9d6793f5c..e22e02812f 100755 --- a/build/tgs.docker.sh +++ b/build/tgs.docker.sh @@ -14,7 +14,6 @@ if [ ! -f $PROD_CONFIG ]; then fi echo "$PROD_CONFIG not detected! Creating empty and running setup wizard..." - # Important, config reloading doesn't work with symlinks echo "{}" > $PROD_CONFIG fi diff --git a/build/tgstation-server.service b/build/tgstation-server.service index bcd6de155d..21c6809372 100644 --- a/build/tgstation-server.service +++ b/build/tgstation-server.service @@ -7,13 +7,17 @@ After=postgresql.service After=mssql-server.service [Service] +Type=notify +NotifyAccess=all ExecStart=/bin/bash /opt/tgstation-server/tgs.sh General:SetupWizardMode=Never +TimeoutStartSec=600 Restart=Always KillMode=process RestartKillSignal=SIGUSR2 AmbientCapabilities=CAP_SYS_NICE StandardOutput=null StandardError=null +WatchdogSec=60 [Install] WantedBy=multi-user.target diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 085a5ce2aa..da057c1ead 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -165,6 +165,7 @@ namespace Tgstation.Server.Host.Components.Byond ActiveVersion?.ToString(), stringVersion, }, + false, cancellationToken); ActiveVersion = version; @@ -475,7 +476,7 @@ namespace Tgstation.Server.Host.Components.Byond progressReporter.StageName = "Running event"; var versionString = version.ToString(); - await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionString }, cancellationToken); + await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionString }, false, cancellationToken); await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken); @@ -484,7 +485,7 @@ namespace Tgstation.Server.Host.Components.Byond catch (Exception ex) { if (ex is not OperationCanceledException) - await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { ex.Message }, cancellationToken); + await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List { ex.Message }, false, cancellationToken); lock (installedVersions) installedVersions.Remove(version); diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index 57c177dbb0..d2c48f3b4d 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -448,7 +448,7 @@ namespace Tgstation.Server.Host.Components.Deployment async Task DeleteCompileJobContent(string directory, CancellationToken cancellationToken) { // Then call the cleanup event, waiting here first - await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List { ioManager.ResolvePath(directory) }, cancellationToken); + await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List { ioManager.ResolvePath(directory) }, true, cancellationToken); await ioManager.DeleteDirectory(directory, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 97d00688c5..38ee725a01 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -386,7 +386,7 @@ namespace Tgstation.Server.Host.Components.Deployment repoName, cancellationToken); - var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty(), cancellationToken); + var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty(), false, cancellationToken); try { @@ -537,7 +537,7 @@ namespace Tgstation.Server.Host.Components.Deployment { // DCT: Cancellation token is for job, delaying here is fine progressReporter.StageName = "Running CompileCancelled event"; - await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty(), CancellationToken.None); + await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty(), true, CancellationToken.None); throw; } finally @@ -594,6 +594,7 @@ namespace Tgstation.Server.Host.Components.Deployment repoOrigin.ToString(), $"{byondLock.Version.Major}.{byondLock.Version.Minor}", }, + true, cancellationToken); // determine the dme @@ -632,6 +633,7 @@ namespace Tgstation.Server.Host.Components.Deployment repoOrigin.ToString(), $"{byondLock.Version.Major}.{byondLock.Version.Minor}", }, + true, cancellationToken); // run compiler @@ -672,6 +674,7 @@ namespace Tgstation.Server.Host.Components.Deployment exitCode == 0 ? "1" : "0", byondVersion.ToString(), }, + true, cancellationToken); throw; } @@ -684,6 +687,7 @@ namespace Tgstation.Server.Host.Components.Deployment resolvedOutputDirectory, byondVersion.ToString(), }, + true, cancellationToken); logger.LogTrace("Applying static game file symlinks..."); @@ -957,7 +961,7 @@ namespace Tgstation.Server.Host.Components.Deployment try { // DCT: None available - await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List { jobPath }, CancellationToken.None); + await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List { jobPath }, true, CancellationToken.None); await ioManager.DeleteDirectory(jobPath, CancellationToken.None); } catch (Exception e) diff --git a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs index 3613f48da8..82f7cdb8d1 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs @@ -31,15 +31,15 @@ namespace Tgstation.Server.Host.Components.Events } /// - public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + public async Task HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(parameters); if (watchdog == null) throw new InvalidOperationException("EventConsumer used without watchdog set!"); - var scriptTask = configuration.HandleEvent(eventType, parameters, cancellationToken); - await watchdog.HandleEvent(eventType, parameters, cancellationToken); + var scriptTask = configuration.HandleEvent(eventType, parameters, deploymentPipeline, cancellationToken); + await watchdog.HandleEvent(eventType, parameters, deploymentPipeline, cancellationToken); await scriptTask; } @@ -49,11 +49,10 @@ namespace Tgstation.Server.Host.Components.Events /// The value of . public void SetWatchdog(IWatchdog watchdog) { -#pragma warning disable IDE0016 // Use 'throw' expression ArgumentNullException.ThrowIfNull(watchdog); -#pragma warning restore IDE0016 // Use 'throw' expression if (this.watchdog != null) throw new InvalidOperationException("watchdog already set!"); + this.watchdog = watchdog; } } diff --git a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs index 5b5acea272..ddd31c90e8 100644 --- a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs @@ -14,8 +14,9 @@ namespace Tgstation.Server.Host.Components.Events /// /// The . /// An of parameters for . + /// If this event is part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken); + Task HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 2f3cc1f1bf..9f5b5ec62e 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -306,9 +306,10 @@ namespace Tgstation.Server.Host.Components // the main point of auto update is to pull the remote await repo.FetchOrigin( + NextProgressReporter("Fetch Origin"), repositorySettings.AccessUser, repositorySettings.AccessToken, - NextProgressReporter("Fetch Origin"), + true, cancellationToken); var hasDbChanges = false; @@ -380,9 +381,10 @@ namespace Tgstation.Server.Host.Components await UpdateRevInfo(repo.Head, false, null); var result = await repo.MergeOrigin( + NextProgressReporter("Merge Origin"), repositorySettings.CommitterName, repositorySettings.CommitterEmail, - NextProgressReporter("Merge Origin"), + true, cancellationToken); var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value; @@ -428,10 +430,11 @@ namespace Tgstation.Server.Host.Components const string StageName = "Resetting to origin..."; logger.LogTrace(StageName); await repo.ResetToOrigin( + NextProgressReporter(StageName), repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.UpdateSubmodules.Value, - NextProgressReporter(StageName), + true, cancellationToken); var currentHead = repo.Head; @@ -451,12 +454,13 @@ namespace Tgstation.Server.Host.Components if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits.Value)) { var pushedOrigin = await repo.Sychronize( + NextProgressReporter("Synchronize"), repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, - NextProgressReporter("Synchronize"), shouldSyncTracked, + true, cancellationToken); var currentHead = repo.Head; if (currentHead != currentRevInfo.CommitSha) @@ -492,7 +496,7 @@ namespace Tgstation.Server.Host.Components { await asyncDelayer.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : minutes), cancellationToken); logger.LogInformation("Beginning auto update..."); - await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), cancellationToken); + await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), true, cancellationToken); try { var repositoryUpdateJob = new Job diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index df1f4217e4..8c7a8df85e 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -281,7 +281,8 @@ namespace Tgstation.Server.Host.Components platformIdentifier, fileTransferService, loggerFactory.CreateLogger(), - generalConfiguration); + generalConfiguration, + sessionConfiguration); var eventConsumer = new EventConsumer(configuration); var repoManager = new RepositoryManager( repositoryFactory, diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index ae54a9a8c7..07c29dc220 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -83,31 +83,35 @@ namespace Tgstation.Server.Host.Components.Repository /// /// Fetch commits from the origin repository. /// + /// The to report progress of the operation. /// The username to fetch from the origin repository. /// The password to fetch from the origin repository. - /// The to report progress of the operation. + /// If any events created should be marked as part of the deployment pipeline. /// The for the operation. /// A representing the running operation. Task FetchOrigin( + JobProgressReporter progressReporter, string username, string password, - JobProgressReporter progressReporter, + bool deploymentPipeline, CancellationToken cancellationToken); /// /// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository. /// + /// The to report progress of the operation. /// The username used for fetching from submodule repositories. /// The password used for fetching from submodule repositories. /// If a submodule update should be attempted after the merge. - /// The to report progress of the operation. + /// If any events created should be marked as part of the deployment pipeline. /// The for the operation. /// A resulting in the SHA of the new HEAD. Task ResetToOrigin( + JobProgressReporter progressReporter, string username, string password, bool updateSubmodules, - JobProgressReporter progressReporter, + bool deploymentPipeline, CancellationToken cancellationToken); /// @@ -122,31 +126,39 @@ namespace Tgstation.Server.Host.Components.Repository /// /// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository. /// + /// The to report progress of the operation. /// The name of the merge committer. /// The e-mail of the merge committer. - /// The to report progress of the operation. + /// If any events created should be marked as part of the deployment pipeline. /// The for the operation. /// A resulting in a representing the merge result that is after a fast forward, on a merge or up to date, on a conflict. - Task MergeOrigin(string committerName, string committerEmail, JobProgressReporter progressReporter, CancellationToken cancellationToken); + Task MergeOrigin( + JobProgressReporter progressReporter, + string committerName, + string committerEmail, + bool deploymentPipeline, + CancellationToken cancellationToken); /// /// Runs the synchronize event script and attempts to push any changes made to the if on a tracked branch. /// + /// The to report progress of the operation. /// The username to fetch from the origin repository. /// The password to fetch from the origin repository. /// The name of the potential committer. /// The e-mail of the potential committer. - /// The to report progress of the operation. /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch. + /// If any events created should be marked as part of the deployment pipeline. /// The for the operation. /// A resulting in if commits were pushed to the tracked origin reference, otherwise. Task Sychronize( + JobProgressReporter progressReporter, string username, string password, string committerName, string committerEmail, - JobProgressReporter progressReporter, bool synchronizeTrackedBranch, + bool deploymentPipeline, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 69c9689deb..2748e5ec6c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -333,6 +333,7 @@ namespace Tgstation.Server.Host.Components.Repository await eventConsumer.HandleEvent( EventType.RepoMergeConflict, arguments, + false, cancellationToken); return new TestMergeResult { @@ -359,6 +360,7 @@ namespace Tgstation.Server.Host.Components.Repository progressReporter.CreateSection("Update Submodules", progressFactor), username, password, + false, cancellationToken); } } @@ -371,6 +373,7 @@ namespace Tgstation.Server.Host.Components.Repository testMergeParameters.TargetCommitSha, testMergeParameters.Comment, }, + false, cancellationToken); return new TestMergeResult @@ -392,7 +395,7 @@ namespace Tgstation.Server.Host.Components.Repository ArgumentNullException.ThrowIfNull(committish); ArgumentNullException.ThrowIfNull(progressReporter); logger.LogDebug("Checkout object: {committish}...", committish); - await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, cancellationToken); + await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, false, cancellationToken); await Task.Factory.StartNew( () => { @@ -411,15 +414,21 @@ namespace Tgstation.Server.Host.Components.Repository progressReporter.CreateSection(null, 1.0 / 3), username, password, + false, cancellationToken); } /// - public async Task FetchOrigin(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken) + public async Task FetchOrigin( + JobProgressReporter progressReporter, + string username, + string password, + bool deploymentPipeline, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(progressReporter); logger.LogDebug("Fetch origin..."); - await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty(), cancellationToken); + await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty(), deploymentPipeline, cancellationToken); await Task.Factory.StartNew( () => { @@ -458,10 +467,11 @@ namespace Tgstation.Server.Host.Components.Repository /// public async Task ResetToOrigin( + JobProgressReporter progressReporter, string username, string password, bool updateSubmodules, - JobProgressReporter progressReporter, + bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(progressReporter); @@ -469,7 +479,7 @@ namespace Tgstation.Server.Host.Components.Repository throw new JobException(ErrorCode.RepoReferenceRequired); logger.LogTrace("Reset to origin..."); var trackedBranch = libGitRepo.Head.TrackedBranch; - await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken); + await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, deploymentPipeline, cancellationToken); await ResetToSha( trackedBranch.Tip.Sha, progressReporter.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0), @@ -480,6 +490,7 @@ namespace Tgstation.Server.Host.Components.Repository progressReporter.CreateSection(null, 1.0 / 3), username, password, + deploymentPipeline, cancellationToken); } @@ -547,9 +558,10 @@ namespace Tgstation.Server.Host.Components.Repository /// public async Task MergeOrigin( + JobProgressReporter progressReporter, string committerName, string committerEmail, - JobProgressReporter progressReporter, + bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(progressReporter); @@ -606,7 +618,17 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { - await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken); + await eventConsumer.HandleEvent( + EventType.RepoMergeConflict, + new List + { + oldTip.Sha, + trackedBranch.Tip.Sha, + oldHead.FriendlyName ?? UnknownReference, + trackedBranch.FriendlyName, + }, + deploymentPipeline, + cancellationToken); return null; } @@ -615,12 +637,13 @@ namespace Tgstation.Server.Host.Components.Repository /// public async Task Sychronize( + JobProgressReporter progressReporter, string username, string password, string committerName, string committerEmail, - JobProgressReporter progressReporter, bool synchronizeTrackedBranch, + bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(committerName); @@ -661,6 +684,7 @@ namespace Tgstation.Server.Host.Components.Repository { ioMananger.ResolvePath(), }, + deploymentPipeline, cancellationToken); } finally @@ -964,9 +988,15 @@ namespace Tgstation.Server.Host.Components.Repository /// of the operation. /// The username for the . /// The password for the . + /// If any events created should be marked as part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - async Task UpdateSubmodules(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken) + async Task UpdateSubmodules( + JobProgressReporter progressReporter, + string username, + string password, + bool deploymentPipeline, + CancellationToken cancellationToken) { var submoduleCount = libGitRepo.Submodules.Count(); if (submoduleCount == 0) @@ -1032,7 +1062,11 @@ namespace Tgstation.Server.Host.Components.Repository } } - await eventConsumer.HandleEvent(EventType.RepoSubmoduleUpdate, new List { submodule.Name }, cancellationToken); + await eventConsumer.HandleEvent( + EventType.RepoSubmoduleUpdate, + new List { submodule.Name }, + deploymentPipeline, + cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs index 53de9c45c3..57ecc8912b 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs @@ -152,11 +152,10 @@ namespace Tgstation.Server.Host.Components.Repository CancellationToken cancellationToken) #pragma warning restore CA1502, CA1506 { - var repoManager = instance.RepositoryManager; - using var repo = await repoManager.LoadRepository(cancellationToken); - if (repo == null) - throw new JobException(ErrorCode.RepoMissing); + _ = job; // shuts up an IDE warning + var repoManager = instance.RepositoryManager; + using var repo = await repoManager.LoadRepository(cancellationToken) ?? throw new JobException(ErrorCode.RepoMissing); var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null; var startReference = repo.Reference; @@ -252,11 +251,21 @@ namespace Tgstation.Server.Host.Components.Repository { if (!repo.Tracking) throw new JobException(ErrorCode.RepoReferenceRequired); - await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter("Fetch Origin"), cancellationToken); + await repo.FetchOrigin( + NextProgressReporter("Fetch Origin"), + currentModel.AccessUser, + currentModel.AccessToken, + false, + cancellationToken); if (!modelHasShaOrReference) { - var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter("Merge Origin"), cancellationToken); + var fastForward = await repo.MergeOrigin( + NextProgressReporter("Merge Origin"), + committerName, + currentModel.CommitterEmail, + false, + cancellationToken); if (!fastForward.HasValue) throw new JobException(ErrorCode.RepoMergeConflict); lastRevisionInfo.OriginCommitSha = await repo.GetOriginSha(cancellationToken); @@ -264,12 +273,13 @@ namespace Tgstation.Server.Host.Components.Repository if (fastForward.Value) { await repo.Sychronize( + NextProgressReporter("Sychronize"), currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, - NextProgressReporter("Sychronize"), true, + false, cancellationToken); postUpdateSha = repo.Head; } @@ -315,18 +325,20 @@ namespace Tgstation.Server.Host.Components.Repository if (!repo.Tracking) throw new JobException(ErrorCode.RepoReferenceNotTracking); await repo.ResetToOrigin( + NextProgressReporter("Reset to Origin"), currentModel.AccessUser, currentModel.AccessToken, updateSubmodules, - NextProgressReporter("Reset to Origin"), + false, cancellationToken); await repo.Sychronize( + NextProgressReporter("Synchronize"), currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, - NextProgressReporter("Synchronize"), true, + false, cancellationToken); await CallLoadRevInfo(); @@ -473,7 +485,7 @@ namespace Tgstation.Server.Host.Components.Repository if (revInfoWereLookingFor != null) { // goteem - logger.LogDebug("Reusing existing SHA {0}...", revInfoWereLookingFor.CommitSha); + logger.LogDebug("Reusing existing SHA {sha}...", revInfoWereLookingFor.CommitSha); await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), cancellationToken); lastRevisionInfo = revInfoWereLookingFor; } @@ -535,11 +547,12 @@ namespace Tgstation.Server.Host.Components.Repository if (currentModel.PushTestMergeCommits.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead))) { await repo.Sychronize( + NextProgressReporter("Synchronize"), currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, - NextProgressReporter("Synchronize"), + false, false, cancellationToken); await UpdateRevInfo(); diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index e0286d7bbc..71756c841f 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -549,6 +549,7 @@ namespace Tgstation.Server.Host.Components.Session { process.Id.ToString(CultureInfo.InvariantCulture), }, + false, cancellationToken); return process; diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 1e7f082fe8..d4d5c27b6f 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -128,6 +128,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// readonly GeneralConfiguration generalConfiguration; + /// + /// The for . + /// + readonly SessionConfiguration sessionConfiguration; + /// /// The for . Also used as a . /// @@ -155,6 +160,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The value of . /// The value of . /// The value of . + /// The value of . public Configuration( IIOManager ioManager, ISynchronousIOManager synchronousIOManager, @@ -164,7 +170,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles IPlatformIdentifier platformIdentifier, IFileTransferTicketProvider fileTransferService, ILogger logger, - GeneralConfiguration generalConfiguration) + GeneralConfiguration generalConfiguration, + SessionConfiguration sessionConfiguration) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); @@ -175,6 +182,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration)); + this.sessionConfiguration = sessionConfiguration ?? throw new ArgumentNullException(nameof(sessionConfiguration)); semaphore = new SemaphoreSlim(1); disposeCts = new CancellationTokenSource(); @@ -592,7 +600,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken); /// - public async Task HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + public async Task HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(parameters); @@ -639,6 +647,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles noShellExecute: true)) using (cancellationToken.Register(() => script.Terminate())) { + if (sessionConfiguration.LowPriorityDeploymentProcesses) + script.AdjustPriority(false); + var exitCode = await script.Lifetime; cancellationToken.ThrowIfCancellationRequested(); var scriptOutput = await script.GetCombinedOutput(cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 43c903f21b..8396ce7c99 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -112,7 +112,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var eventType = Server.TerminationWasRequested ? EventType.WorldEndProcess : EventType.WatchdogCrash; - await HandleEvent(eventType, Enumerable.Empty(), false, cancellationToken); + await HandleEventImpl(eventType, Enumerable.Empty(), false, cancellationToken); var exitWord = Server.TerminationWasRequested ? "exited" : "crashed"; if (Server.RebootState == Session.RebootState.Shutdown) @@ -143,7 +143,7 @@ namespace Tgstation.Server.Host.Components.Watchdog gracefulRebootRequired = false; Server.ResetRebootState(); - var eventTask = HandleEvent(EventType.WorldReboot, Enumerable.Empty(), false, cancellationToken); + var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty(), false, cancellationToken); try { switch (rebootState) @@ -174,7 +174,7 @@ namespace Tgstation.Server.Host.Components.Watchdog await HandleNewDmbAvailable(cancellationToken); break; case MonitorActivationReason.ActiveServerPrimed: - await HandleEvent(EventType.WorldPrime, Enumerable.Empty(), false, cancellationToken); + await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty(), false, cancellationToken); break; case MonitorActivationReason.ActiveServerStartup: break; // unused in BasicWatchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 636f9df42a..c13ea5bfb7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -449,7 +449,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, CancellationToken cancellationToken) + async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(parameters); @@ -509,7 +509,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ? "Launching..." : "Reattaching..."); // simple announce if (reattachInfo == null) - eventTask = HandleEvent(EventType.WatchdogLaunch, Enumerable.Empty(), false, cancellationToken); + eventTask = HandleEventImpl(EventType.WatchdogLaunch, Enumerable.Empty(), false, cancellationToken); } // since neither server is running, this is safe to do @@ -693,13 +693,13 @@ namespace Tgstation.Server.Host.Components.Watchdog /// If the event should be sent to DreamDaemon. /// The for the operation. /// A representing the running operation. - protected async Task HandleEvent(EventType eventType, IEnumerable parameters, bool relayToSession, CancellationToken cancellationToken) + protected async Task HandleEventImpl(EventType eventType, IEnumerable parameters, bool relayToSession, CancellationToken cancellationToken) { try { - var sessionEventTask = relayToSession ? ((IEventConsumer)this).HandleEvent(eventType, parameters, cancellationToken) : Task.CompletedTask; + var sessionEventTask = relayToSession ? ((IEventConsumer)this).HandleEvent(eventType, parameters, false, cancellationToken) : Task.CompletedTask; await Task.WhenAll( - eventConsumer.HandleEvent(eventType, parameters, cancellationToken), + eventConsumer.HandleEvent(eventType, parameters, false, cancellationToken), sessionEventTask); } catch (JobException ex) @@ -1005,7 +1005,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; if (!graceful) { - var eventTask = HandleEvent( + var eventTask = HandleEventImpl( releaseServers ? EventType.WatchdogDetach : EventType.WatchdogShutdown, diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index dde2ac996a..ef312928a0 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -17,7 +17,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; using Newtonsoft.Json; @@ -337,6 +336,9 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); } // configure file transfer services @@ -421,14 +423,6 @@ namespace Tgstation.Server.Host.Core logger.LogDebug("Content Root: {contentRoot}", hostingEnvironment.ContentRootPath); logger.LogTrace("Web Root: {webRoot}", hostingEnvironment.WebRootPath); - // attempt to restart the server if the configuration changes - if (serverControl.WatchdogPresent) - ChangeToken.OnChange(Configuration.GetReloadToken, () => - { - logger.LogInformation("Configuration change detected"); - serverControl.Restart(); - }); - // setup the HTTP request pipeline // Add additional logging context to the request applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration); diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 01c1c752b2..9dddebff23 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -48,14 +49,24 @@ namespace Tgstation.Server.Host { ArgumentNullException.ThrowIfNull(args); + // need to shove this arg in to disable config reloading unless a user specifically overrides it + if (!args.Any(arg => arg.Contains("hostBuilder:reloadConfigOnChange", StringComparison.OrdinalIgnoreCase)) + && String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("hostBuilder__reloadConfigOnChange"))) + { + var oldArgs = args; + args = new string[oldArgs.Length + 1]; + Array.Copy(oldArgs, args, oldArgs.Length); + args[oldArgs.Length] = "--hostBuilder:reloadConfigOnChange=false"; + } + var basePath = IOManager.ResolvePath(); IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { builder.SetBasePath(basePath); - builder.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) - .AddYamlFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: true); + builder.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: false) + .AddYamlFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: false); // reorganize the builder so our yaml configs don't override the env/cmdline configs // values obtained via debugger diff --git a/src/Tgstation.Server.Host/System/NativeMethods.cs b/src/Tgstation.Server.Host/System/NativeMethods.cs index 60b97ca90c..dacb4351bc 100644 --- a/src/Tgstation.Server.Host/System/NativeMethods.cs +++ b/src/Tgstation.Server.Host/System/NativeMethods.cs @@ -127,5 +127,15 @@ namespace Tgstation.Server.Host.System IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); + + /// + /// See https://www.freedesktop.org/software/systemd/man/sd_notify.html. + /// +#pragma warning disable IDE0079 +#pragma warning disable CA2101 // https://github.com/dotnet/roslyn-analyzers/issues/5479#issuecomment-1603665900 + [DllImport("libsystemd.so.0", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern int sd_notify(int unset_environment, [MarshalAs(UnmanagedType.LPUTF8Str)] string state); +#pragma warning restore CA2101 +#pragma warning restore IDE0079 } } diff --git a/src/Tgstation.Server.Host/System/SystemDManager.cs b/src/Tgstation.Server.Host/System/SystemDManager.cs new file mode 100644 index 0000000000..5dd66e259e --- /dev/null +++ b/src/Tgstation.Server.Host/System/SystemDManager.cs @@ -0,0 +1,217 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using Mono.Unix; + +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Extensions; + +namespace Tgstation.Server.Host.System +{ + /// + /// Implements the SystemD notify service protocol. + /// + sealed class SystemDManager : IHostedService, IRestartHandler, IDisposable + { + /// + /// The for the . + /// + readonly IHostApplicationLifetime applicationLifetime; + + /// + /// The for the . + /// + readonly IInstanceManager instanceManager; + + /// + /// The for the . + /// + readonly IRestartRegistration restartRegistration; + + /// + /// The for the . + /// + readonly ILogger logger; + + /// + /// The for . + /// + readonly CancellationTokenSource watchdogCts; + + /// + /// The main task executing in the . + /// + Task runTask; + + /// + /// If TGS is going to restart. + /// + bool restartInProgress; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + /// The used to create the . + /// The value of . + public SystemDManager( + IHostApplicationLifetime applicationLifetime, + IInstanceManager instanceManager, + IServerControl serverControl, + ILogger logger) + { + this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + + ArgumentNullException.ThrowIfNull(serverControl); + + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + restartRegistration = serverControl.RegisterForRestart(this); + try + { + watchdogCts = new CancellationTokenSource(); + } + catch + { + restartRegistration.Dispose(); + throw; + } + } + + /// + public void Dispose() + { + restartRegistration.Dispose(); + watchdogCts.Dispose(); + } + + /// + public Task HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken) + { + // If this is set, we know a gracefule SHUTDOWN was requested + restartInProgress = !handlerMayDelayShutdownWithExtremelyLongRunningTasks; + return Task.CompletedTask; + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + if (SendSDNotify("RELOADING=1")) + { + logger.LogDebug("SystemD detected"); + runTask = RunAsync(watchdogCts.Token); + } + else + { + logger.LogDebug("SystemD not detected"); + runTask = Task.CompletedTask; + } + + return Task.CompletedTask; + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + watchdogCts.Cancel(); + await runTask.WithToken(cancellationToken); + } + + /// + /// Runs the . + /// + /// The for the operation. + /// A representing the running operation. + async Task RunAsync(CancellationToken cancellationToken) + { + if (applicationLifetime.ApplicationStarted.IsCancellationRequested) + throw new InvalidOperationException("RunAsync called after application started!"); + + logger.LogTrace("Installing lifetime handlers..."); + + var readyCounts = 0; + void CheckReady() + { + if (Interlocked.Increment(ref readyCounts) < 2) + return; + + SendSDNotify("READY=1"); + } + + applicationLifetime.ApplicationStarted.Register(() => CheckReady()); + applicationLifetime.ApplicationStopping.Register( + () => SendSDNotify( + restartInProgress + ? "RELOADING=1" + : "STOPPING=1")); + + try + { + await instanceManager.Ready.WithToken(cancellationToken); + CheckReady(); + + var watchdogUsec = Environment.GetEnvironmentVariable("WATCHDOG_USEC"); + if (String.IsNullOrWhiteSpace(watchdogUsec)) + { + logger.LogDebug("WATCHDOG_USEC not present, not starting watchdog loop"); + return; + } + + logger.LogDebug("Starting watchdog loop with interval of {usec}us", watchdogUsec); + + var microseconds = UInt64.Parse(watchdogUsec, CultureInfo.InvariantCulture); + var milliseconds = (int)(microseconds / 1000); + if (milliseconds == 0) + milliseconds = 1; + + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(milliseconds, cancellationToken); + + logger.LogTrace("Sending sd_notify WATCHDOG=1"); + var result = NativeMethods.sd_notify(0, "WATCHDOG=1"); + if (result <= 0) + logger.LogError(new UnixIOException(result), "sd_notify READY=1 failed!"); + } + } + catch (OperationCanceledException ex) + { + logger.LogTrace(ex, "Watchdog loop cancelled!"); + } + catch (Exception ex) + { + logger.LogError(ex, "Watchdog loop crashed!"); + } + + logger.LogDebug("Exited watchdog loop"); + } + + /// + /// Send a sd_notify . + /// + /// The to send via sd_notify. + /// if the command succeeded, otherwise. + bool SendSDNotify(string command) + { + logger.LogTrace("Sending sd_notify {message}...", command); + var result = NativeMethods.sd_notify(0, command); + if (result > 0) + return true; + + if (result < 0) + logger.LogError(new UnixIOException(result), "sd_notify READY=1 failed!"); + else + logger.LogTrace("Could not send sd_notify {message}. Socket closed!", command); + + return false; + } + } +} diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml index 98e98062a8..273bebda9d 100644 --- a/src/Tgstation.Server.Host/appsettings.yml +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -1,59 +1,66 @@ General: - MinimumPasswordLength: 15 - GitHubAccessToken: null - SetupWizardMode: AutoDetect - ByondTopicTimeout: 5000 - RestartTimeoutMinutes: 1 - ApiPort: 5000 - UseBasicWatchdog: false - UserLimit: 100 - UserGroupLimit: 25 - InstanceLimit: 10 - ValidInstancePaths: - HostApiDocumentation: false - SkipAddingByondFirewallException: false - DeploymentDirectoryCopyTasksPerCore: 100 +# ConfigVersion: # Basic semver. Differs from TGS version to version. See changelog for current version + MinimumPasswordLength: 15 # Minimum TGS user password length + GitHubAccessToken: # GitHub personal access token with no scopes used to bypass rate-limits + SetupWizardMode: AutoDetect # If the interactive TGS setup wizard should run + ByondTopicTimeout: 5000 # Timeout for BYOND /world/Topic() calls in milliseconds + RestartTimeoutMinutes: 1 # Timeout for server restarts after requested by SIGTERM or the HTTP API + ApiPort: 5000 # Port the HTTP API is hosted on + UseBasicWatchdog: false # The basic watchdog hard restarts DreamDaemon when /world/proc/TgsReboot() is called in the DMAPI if a new deployment is available + UserLimit: 100 # Maximum number of allowed users + UserGroupLimit: 25 # Maximum number of allowed groups + InstanceLimit: 10 # Maximum number of allowed instances + ValidInstancePaths: # An array of directories instances may be created in (either directly or as a subdirectory). null removes the restriction + HostApiDocumentation: false # Make HTTP API documentation available at /swagger/v1/swagger.json + SkipAddingByondFirewallException: false # Windows Only: Prevent running netsh.exe to add a firewall exception for installed DreamDaemon binaries + DeploymentDirectoryCopyTasksPerCore: 100 # Maximum number of concurrent file copy operations PER available CPU core Session: - HighPriorityLiveDreamDaemon: false - LowPriorityDeploymentProcesses: true + HighPriorityLiveDreamDaemon: false # If DreamDaemon instances should run as higher priority processes + LowPriorityDeploymentProcesses: true # If TGS Deployments should run as lower priority processes FileLogging: - Directory: - Disable: false - LogLevel: Debug - MicrosoftLogLevel: Warning + Directory: # Directory in which log files are stored. Windows default: %PROGRAMDATA%/tgstation-server. Linux default: /var/log/tgstation-server + Disable: false # Disable file logging entirely + LogLevel: Debug # Level of file logging verbosity. Can be one of Trace, Debug, Information, Warning, Error, or Critical + MicrosoftLogLevel: Warning # Level of file logging verbosity from Microsoft dependecies (i.e. webserver, database object relational mapper, etc...). Can be one of Trace, Debug, Information, Warning, Error, or Critical Logging: - IncludeScopes: false Debug: LogLevel: - Default: Debug - Microsoft: Information + Default: Debug # Level of debugger logging verbosity. Can be one of Trace, Debug, Information, Warning, Error, or Critical. + Microsoft: Information # Level of file logging verbosity from Microsoft dependecies (i.e. webserver, database object relational mapper, etc...). Can be one of Trace, Debug, Information, Warning, Error, or Critical Console: LogLevel: - Default: Trace - Microsoft: Warning + Default: Trace # Level of stdout logging verbosity. Can be one of Trace, Debug, Information, Warning, Error, or Critical. + Microsoft: Warning # Level of stdout verbosity from Microsoft dependecies (i.e. webserver, database object relational mapper, etc...). Can be one of Trace, Debug, Information, Warning, Error, or Critical ControlPanel: - Enable: false - Channel: https://tgstation.github.io/tgstation-server-webpanel/api/${Major}.${Minor}.${Patch} - AllowAnyOrigin: false - AllowedOrigins: [] - PublicPath: + Enable: false # If the web control panel is hosted alongside the server + Channel: https://tgstation.github.io/tgstation-server-webpanel/api/${Major}.${Minor}.${Patch} # Channel for live web control panel updates. The tokens ${Major}, ${Minor}, and ${Patch} are substituted for the version of the HTTP API TGS was built with + AllowAnyOrigin: false # Enable the `Access-Control-Allow-Origin: *` header for HTTP responses + AllowedOrigins: [] # Explict list of origins for `Access-Control-Allow-Origin:` HTTP header. Ignored if AllowAnyOrigin is true + PublicPath: # Used if TGS is not hosted at the website root. If TGS is hosted under `https://domain.com/tgs`, this should be `/tgs` Updates: - GitHubRepositoryId: 92952846 - GitTagPrefix: tgstation-server-v - UpdatePackageAssetName: ServerUpdatePackage.zip + GitHubRepositoryId: 92952846 # GitHub repostiory ID where TGS updates can be found in releases + GitTagPrefix: tgstation-server-v # Git tag prefix used for locating server update releases + UpdatePackageAssetName: ServerUpdatePackage.zip # Name of the .zip file asset that contains the server update Database: - DropDatabase: false - DatabaseType: SqlServer - ResetAdminPassword: false - ServerVersion: - ConnectionString: Data Source=(local);Initial Catalog=TGS;Integrated Security=True + DatabaseType: SqlServer # The database type TGS connects to + ServerVersion: # The version of the database being connected to, generally not required to be specified + ConnectionString: Data Source=(local);Initial Catalog=TGS;Integrated Security=True # The connection string used to establish the database connection. Format varies for each DatabaseType + DropDatabase: false # DANGEROUS! Causes TGS to recreate its database on startup. Must be unset manually + ResetAdminPassword: false # DANGEROUS! Causes TGS to reset the `Admin` user password back to its default value on startup. Must be unset manually. Security: - TokenExpiryMinutes: 15 - TokenClockSkewMinutes: 1 - TokenSigningKeyByteAmount: 256 - CustomTokenSigningKeyBase64: + TokenExpiryMinutes: 15 # Length of time in minutes a login session token is valid for + TokenClockSkewMinutes: 1 # Clock skew allowance for validating loging tokens + TokenSigningKeyByteAmount: 256 # Length of generated token signing key in bytes. Ignored if CustomTokenSigningKeyBase64 is set + CustomTokenSigningKeyBase64: # OAuth: - GitHub: - Discord: - TGForums: - Keycloak: +# Example: +# RedirectUrl: # Return URL for OAuth handshake. For a server hosted at `https://domain.com/tgs`, this should be in the format `https://domain.com/tgs/app/` +# ClientId: # OAuth client ID +# ClientSecret: # OAuth client secret +# ServerUrl: # Only used by Keycloak and InvisionCommunity. Server URL (Includes Keycloak realm) +# UserInformationUrlOverride: # Not supported by GitHub. Overrides the URL TGS uses to retrieve a user's information + GitHub: # https://github.com OAuth configuration + Discord: # https://discord.com OAuth configuration + TGForums: # https://tgstation13.org OAuth configuration + Keycloak: # Keycloak OAuth configuration. + InvisionCommunity: # Invision Community OAuth configuration. diff --git a/tgstation-server.sln b/tgstation-server.sln index b7082a442b..e7206b2cd2 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -206,6 +206,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deb", "deb", "{457A1F89-620 ProjectSection(SolutionItems) = preProject build\package\deb\build_package.sh = build\package\deb\build_package.sh build\package\deb\MakeInstall = build\package\deb\MakeInstall + build\package\deb\tgs-configure = build\package\deb\tgs-configure build\package\deb\wrap_gpg.sh = build\package\deb\wrap_gpg.sh EndProjectSection EndProject