mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-25 14:06:41 +01:00
Merge pull request #1570 from tgstation/MoreDebGotchas
Removes Configuration auto-reloading. Make deployment event script respect low priority config.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh -e
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
pushd /opt/tgstation-server
|
||||
dotnet /opt/tgstation-server/lib/Default/Tgstation.Server.Host.dll General:SetupWizardMode=Only
|
||||
popd
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string> { versionString }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List<string> { 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<string> { ex.Message }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List<string> { ex.Message }, false, cancellationToken);
|
||||
|
||||
lock (installedVersions)
|
||||
installedVersions.Remove(version);
|
||||
|
||||
@@ -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<string> { ioManager.ResolvePath(directory) }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { ioManager.ResolvePath(directory) }, true, cancellationToken);
|
||||
await ioManager.DeleteDirectory(directory, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
repoName,
|
||||
cancellationToken);
|
||||
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty<string>(), cancellationToken);
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty<string>(), 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<string>(), CancellationToken.None);
|
||||
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty<string>(), 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<string> { jobPath }, CancellationToken.None);
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { jobPath }, true, CancellationToken.None);
|
||||
await ioManager.DeleteDirectory(jobPath, CancellationToken.None);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -31,15 +31,15 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> 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
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/>.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
/// </summary>
|
||||
/// <param name="eventType">The <see cref="EventType"/>.</param>
|
||||
/// <param name="parameters">An <see cref="IEnumerable{T}"/> of <see cref="string"/> parameters for <paramref name="eventType"/>.</param>
|
||||
/// <param name="deploymentPipeline">If this event is part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken);
|
||||
Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string>(), cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty<string>(), true, cancellationToken);
|
||||
try
|
||||
{
|
||||
var repositoryUpdateJob = new Job
|
||||
|
||||
@@ -281,7 +281,8 @@ namespace Tgstation.Server.Host.Components
|
||||
platformIdentifier,
|
||||
fileTransferService,
|
||||
loggerFactory.CreateLogger<StaticFiles.Configuration>(),
|
||||
generalConfiguration);
|
||||
generalConfiguration,
|
||||
sessionConfiguration);
|
||||
var eventConsumer = new EventConsumer(configuration);
|
||||
var repoManager = new RepositoryManager(
|
||||
repositoryFactory,
|
||||
|
||||
@@ -83,31 +83,35 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Fetch commits from the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username to fetch from the origin repository.</param>
|
||||
/// <param name="password">The password to fetch from the origin repository.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task FetchOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username used for fetching from submodule repositories.</param>
|
||||
/// <param name="password">The password used for fetching from submodule repositories.</param>
|
||||
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD.</returns>
|
||||
Task ResetToOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
bool updateSubmodules,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -122,31 +126,39 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="committerName">The name of the merge committer.</param>
|
||||
/// <param name="committerEmail">The e-mail of the merge committer.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward, <see langword="false"/> on a merge or up to date, <see langword="null"/> on a conflict.</returns>
|
||||
Task<bool?> MergeOrigin(string committerName, string committerEmail, JobProgressReporter progressReporter, CancellationToken cancellationToken);
|
||||
Task<bool?> MergeOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the synchronize event script and attempts to push any changes made to the <see cref="IRepository"/> if on a tracked branch.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username to fetch from the origin repository.</param>
|
||||
/// <param name="password">The password to fetch from the origin repository.</param>
|
||||
/// <param name="committerName">The name of the potential committer.</param>
|
||||
/// <param name="committerEmail">The e-mail of the potential committer.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise.</returns>
|
||||
Task<bool> Sychronize(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
JobProgressReporter progressReporter,
|
||||
bool synchronizeTrackedBranch,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<string> { committish }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<string>(), cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty<string>(), deploymentPipeline, cancellationToken);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
@@ -458,10 +467,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
/// <inheritdoc />
|
||||
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<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { 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
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool?> 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<string> { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(
|
||||
EventType.RepoMergeConflict,
|
||||
new List<string>
|
||||
{
|
||||
oldTip.Sha,
|
||||
trackedBranch.Tip.Sha,
|
||||
oldHead.FriendlyName ?? UnknownReference,
|
||||
trackedBranch.FriendlyName,
|
||||
},
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -615,12 +637,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> 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
|
||||
/// <param name="progressReporter"><see cref="JobProgressReporter"/> of the operation.</param>
|
||||
/// <param name="username">The username for the <see cref="credentialsProvider"/>.</param>
|
||||
/// <param name="password">The password for the <see cref="credentialsProvider"/>.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
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<string> { submodule.Name }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(
|
||||
EventType.RepoSubmoduleUpdate,
|
||||
new List<string> { submodule.Name },
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -549,6 +549,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
{
|
||||
process.Id.ToString(CultureInfo.InvariantCulture),
|
||||
},
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
return process;
|
||||
|
||||
@@ -128,6 +128,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for <see cref="Configuration"/>.
|
||||
/// </summary>
|
||||
readonly SessionConfiguration sessionConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for <see cref="Configuration"/>. Also used as a <see langword="lock"/> <see cref="object"/>.
|
||||
/// </summary>
|
||||
@@ -155,6 +160,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="sessionConfiguration">The value of <see cref="sessionConfiguration"/>.</param>
|
||||
public Configuration(
|
||||
IIOManager ioManager,
|
||||
ISynchronousIOManager synchronousIOManager,
|
||||
@@ -164,7 +170,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
ILogger<Configuration> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> 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);
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var eventType = Server.TerminationWasRequested
|
||||
? EventType.WorldEndProcess
|
||||
: EventType.WatchdogCrash;
|
||||
await HandleEvent(eventType, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
await HandleEventImpl(eventType, Enumerable.Empty<string>(), 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<string>(), false, cancellationToken);
|
||||
var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty<string>(), 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<string>(), false, cancellationToken);
|
||||
await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
break;
|
||||
case MonitorActivationReason.ActiveServerStartup:
|
||||
break; // unused in BasicWatchdog
|
||||
|
||||
@@ -449,7 +449,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable<string> 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<string>(), false, cancellationToken);
|
||||
eventTask = HandleEventImpl(EventType.WatchdogLaunch, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
}
|
||||
|
||||
// since neither server is running, this is safe to do
|
||||
@@ -693,13 +693,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="relayToSession">If the event should be sent to DreamDaemon.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool relayToSession, CancellationToken cancellationToken)
|
||||
protected async Task HandleEventImpl(EventType eventType, IEnumerable<string> 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,
|
||||
|
||||
@@ -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<INetworkPromptReaper, PosixNetworkPromptReaper>();
|
||||
|
||||
services.AddSingleton<IHostedService, PosixSignalHandler>();
|
||||
|
||||
services.AddSingleton<SystemDManager>();
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<SystemDManager>());
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -127,5 +127,15 @@ namespace Tgstation.Server.Host.System
|
||||
IntPtr expParam,
|
||||
IntPtr userStreamParam,
|
||||
IntPtr callbackParam);
|
||||
|
||||
/// <summary>
|
||||
/// See https://www.freedesktop.org/software/systemd/man/sd_notify.html.
|
||||
/// </summary>
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the SystemD notify service protocol.
|
||||
/// </summary>
|
||||
sealed class SystemDManager : IHostedService, IRestartHandler, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IHostApplicationLifetime"/> for the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
readonly IHostApplicationLifetime applicationLifetime;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceManager"/> for the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
readonly IInstanceManager instanceManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRestartRegistration"/> for the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
readonly IRestartRegistration restartRegistration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<SystemDManager> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CancellationTokenSource"/> for <see cref="runTask"/>.
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource watchdogCts;
|
||||
|
||||
/// <summary>
|
||||
/// The main task executing in the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
Task runTask;
|
||||
|
||||
/// <summary>
|
||||
/// If TGS is going to restart.
|
||||
/// </summary>
|
||||
bool restartInProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemDManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationLifetime">The value of <see cref="applicationLifetime"/>.</param>
|
||||
/// <param name="instanceManager">The value of <see cref="instanceManager"/>.</param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> used to create the <see cref="restartRegistration"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="ILogger"/>.</param>
|
||||
public SystemDManager(
|
||||
IHostApplicationLifetime applicationLifetime,
|
||||
IInstanceManager instanceManager,
|
||||
IServerControl serverControl,
|
||||
ILogger<SystemDManager> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
restartRegistration.Dispose();
|
||||
watchdogCts.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
watchdogCts.Cancel();
|
||||
await runTask.WithToken(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the <see cref="SystemDManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a sd_notify <paramref name="command"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The <see cref="string"/> to send via sd_notify.</param>
|
||||
/// <returns><see langword="true"/> if the command succeeded, <see langword="false"/> otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user