mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 15:07:03 +01:00
354 lines
9.9 KiB
C#
354 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Net;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using TGServiceInterface;
|
|
using TGServiceInterface.Components;
|
|
|
|
namespace TGServerService
|
|
{
|
|
sealed partial class ServerInstance : ITGByond
|
|
{
|
|
/// <summary>
|
|
/// The instance directory to store the BYOND installation
|
|
/// </summary>
|
|
const string ByondDirectory = "BYOND";
|
|
/// <summary>
|
|
/// The instance directory to use when updating the BYOND installation
|
|
/// </summary>
|
|
const string StagingDirectory = "BYOND_staged";
|
|
/// <summary>
|
|
/// Path to the actual BYOND installation within the <see cref="StagingDirectory"/>
|
|
/// </summary>
|
|
const string StagingDirectoryInner = StagingDirectory + "/byond";
|
|
/// <summary>
|
|
/// The path in the instance directory to store the downloaded BYOND revision
|
|
/// </summary>
|
|
const string RevisionDownloadPath = "BYONDRevision.zip";
|
|
/// <summary>
|
|
/// The location of the BYOND version data of an installation
|
|
/// </summary>
|
|
const string VersionFile = "/byond_version.dat";
|
|
/// <summary>
|
|
/// The URL format string for getting BYOND version {0}.{1} zipfile
|
|
/// </summary>
|
|
const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
|
/// <summary>
|
|
/// The URL for getting the latest BYOND version zipfile
|
|
/// </summary>
|
|
const string ByondLatestURL = "https://secure.byond.com/download/build/LATEST/";
|
|
/// <summary>
|
|
/// The instance directory to modify the BYOND cfg before installation
|
|
/// </summary>
|
|
const string ByondConfigDir = StagingDirectory + "/BYOND/cfg";
|
|
/// <summary>
|
|
/// BYOND's DreamDaemon config file in the cfg modification directory
|
|
/// </summary>
|
|
const string ByondDDConfig = ByondConfigDir + "/daemon.txt";
|
|
/// <summary>
|
|
/// Setting to add to <see cref="ByondDDConfig"/> to suppress an invisible user prompt for running a trusted mode .dmb
|
|
/// </summary>
|
|
const string ByondNoPromptTrustedMode = "trusted-check 0";
|
|
|
|
/// <summary>
|
|
/// The status of the BYOND updater
|
|
/// </summary>
|
|
ByondStatus updateStat = ByondStatus.Idle;
|
|
|
|
/// <summary>
|
|
/// Used for multithreading safety
|
|
/// </summary>
|
|
object ByondLock = new object();
|
|
/// <summary>
|
|
/// The last error the BYOND updater encountered
|
|
/// </summary>
|
|
string lastError;
|
|
/// <summary>
|
|
/// Thread used for staging BYOND revisions
|
|
/// </summary>
|
|
Thread RevisionStaging;
|
|
|
|
/// <summary>
|
|
/// Called when the <see cref="ServerInstance"/> is setup. Prepares the BYOND updater
|
|
/// </summary>
|
|
void InitByond()
|
|
{
|
|
CleanByondStaging();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans the BYOND staging directory
|
|
/// </summary>
|
|
void CleanByondStaging()
|
|
{
|
|
var rrdp = RelativePath(RevisionDownloadPath);
|
|
//linger not
|
|
if (File.Exists(rrdp))
|
|
File.Delete(rrdp);
|
|
Program.DeleteDirectory(RelativePath(StagingDirectory));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the <see cref="ServerInstance"/> is shutdown
|
|
/// </summary>
|
|
void DisposeByond()
|
|
{
|
|
lock (ByondLock)
|
|
{
|
|
if (RevisionStaging != null)
|
|
RevisionStaging.Abort();
|
|
CleanByondStaging();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the updater is considered busy
|
|
/// </summary>
|
|
/// <returns><see langword="true"/> if the updater is considered busy, <see langword="false"/> otherwise</returns>
|
|
bool BusyCheck()
|
|
{
|
|
lock (ByondLock)
|
|
switch (updateStat)
|
|
{
|
|
default:
|
|
case ByondStatus.Starting:
|
|
case ByondStatus.Downloading:
|
|
case ByondStatus.Staging:
|
|
case ByondStatus.Updating:
|
|
return true;
|
|
case ByondStatus.Idle:
|
|
case ByondStatus.Staged:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ByondStatus CurrentStatus()
|
|
{
|
|
lock (ByondLock)
|
|
{
|
|
return updateStat;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string GetError()
|
|
{
|
|
lock (ByondLock)
|
|
{
|
|
var error = lastError;
|
|
lastError = null;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string GetVersion(ByondVersion type)
|
|
{
|
|
try
|
|
{
|
|
lock (ByondLock)
|
|
{
|
|
if (type == ByondVersion.Latest)
|
|
{
|
|
//get the latest version from the website
|
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ByondLatestURL);
|
|
var results = new List<string>();
|
|
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
|
{
|
|
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
|
|
{
|
|
string html = reader.ReadToEnd();
|
|
|
|
Regex regex = new Regex("\\\"([^\"]*)\\\"");
|
|
MatchCollection matches = regex.Matches(html);
|
|
foreach (Match match in matches)
|
|
if (match.Success && match.Value.Contains("_byond.exe"))
|
|
results.Add(match.Value.Replace("\"", "").Replace("_byond.exe", ""));
|
|
}
|
|
}
|
|
results.Sort();
|
|
results.Reverse();
|
|
return results.Count > 0 ? results[0] : null;
|
|
}
|
|
else
|
|
{
|
|
string DirToUse = RelativePath(type == ByondVersion.Staged ? StagingDirectoryInner : ByondDirectory);
|
|
if (Directory.Exists(DirToUse))
|
|
{
|
|
string file = DirToUse + VersionFile;
|
|
if (File.Exists(file))
|
|
return File.ReadAllText(file);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return "Error: " + e.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Downloads and unzips a BYOND revision. Calls <see cref="ApplyStagedUpdate"/> afterwards if the <see cref="ServerInstance"/> isn't running, otherwise, calls <see cref="RequestRestart"/>. Sets <see cref="lastError"/> on failure
|
|
/// </summary>
|
|
/// <param name="param">Stringified BYOND revision</param>
|
|
public void UpdateToVersionImpl(object param)
|
|
{
|
|
lock (ByondLock) {
|
|
if (updateStat != ByondStatus.Starting)
|
|
return;
|
|
updateStat = ByondStatus.Downloading;
|
|
}
|
|
|
|
try
|
|
{
|
|
CleanByondStaging();
|
|
|
|
var vi = ((string)param).Split('.');
|
|
var major = Convert.ToInt32(vi[0]);
|
|
var minor = Convert.ToInt32(vi[1]);
|
|
var rrdp = RelativePath(RevisionDownloadPath);
|
|
using (var client = new WebClient())
|
|
{
|
|
SendMessage(String.Format("BYOND: Updating to version {0}.{1}...", major, minor), MessageType.DeveloperInfo);
|
|
|
|
//DOWNLOADING
|
|
|
|
try
|
|
{
|
|
client.DownloadFile(String.Format(ByondRevisionsURL, major, minor), rrdp);
|
|
}
|
|
catch
|
|
{
|
|
SendMessage("BYOND: Update download failed. Does the specified version exist?", MessageType.DeveloperInfo);
|
|
lastError = String.Format("Download of BYOND version {0}.{1} failed! Does it exist?", major, minor);
|
|
WriteWarning(String.Format("Failed to update BYOND to version {0}.{1}!", major, minor), EventID.BYONDUpdateFail);
|
|
lock (ByondLock)
|
|
{
|
|
updateStat = ByondStatus.Idle;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
lock (ByondLock)
|
|
{
|
|
updateStat = ByondStatus.Staging;
|
|
}
|
|
|
|
//STAGING
|
|
|
|
ZipFile.ExtractToDirectory(rrdp, RelativePath(StagingDirectory));
|
|
lock (ByondLock)
|
|
{
|
|
File.WriteAllText(RelativePath(StagingDirectoryInner + VersionFile), String.Format("{0}.{1}", major, minor));
|
|
//IMPORTANT: SET THE BYOND CONFIG TO NOT PROMPT FOR TRUSTED MODE REEE
|
|
Directory.CreateDirectory(RelativePath(ByondConfigDir));
|
|
File.WriteAllText(RelativePath(ByondDDConfig), ByondNoPromptTrustedMode);
|
|
}
|
|
File.Delete(RevisionDownloadPath);
|
|
|
|
lock (ByondLock)
|
|
{
|
|
updateStat = ByondStatus.Staged;
|
|
}
|
|
|
|
switch (DaemonStatus())
|
|
{
|
|
case DreamDaemonStatus.Offline:
|
|
if(ApplyStagedUpdate())
|
|
lastError = null;
|
|
else
|
|
lastError = "Failed to apply update!";
|
|
break;
|
|
default:
|
|
RequestRestart();
|
|
lastError = "Update staged. Awaiting server restart...";
|
|
SendMessage(String.Format("BYOND: Staging complete. Awaiting server restart...", major, minor), MessageType.DeveloperInfo);
|
|
WriteInfo(String.Format("BYOND update {0}.{1} staged", major, minor), EventID.BYONDUpdateStaged);
|
|
break;
|
|
}
|
|
}
|
|
catch (ThreadAbortException)
|
|
{
|
|
return;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
WriteError("Revision staging errror: " + e.ToString(), EventID.BYONDUpdateFail);
|
|
lock (ByondLock)
|
|
{
|
|
updateStat = ByondStatus.Idle;
|
|
lastError = e.ToString();
|
|
RevisionStaging = null;
|
|
}
|
|
}
|
|
}
|
|
/// <inheritdoc />
|
|
public bool UpdateToVersion(int major, int minor)
|
|
{
|
|
lock (ByondLock)
|
|
{
|
|
if (!BusyCheck())
|
|
{
|
|
updateStat = ByondStatus.Starting;
|
|
RevisionStaging = new Thread(new ParameterizedThreadStart(UpdateToVersionImpl))
|
|
{
|
|
IsBackground = true //don't slow me down
|
|
};
|
|
RevisionStaging.Start(String.Format("{0}.{1}", major, minor));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to move the staged update from <see cref="StagingDirectory"/> to <see cref="ByondDirectory"/>. Sets <see cref="lastError"/> on failure
|
|
/// </summary>
|
|
/// <returns><see langword="true"/> on success, <see langword="false"/> on failure</returns>
|
|
bool ApplyStagedUpdate()
|
|
{
|
|
lock (CompilerLock)
|
|
{
|
|
if (compilerCurrentStatus == CompilerStatus.Compiling)
|
|
return false;
|
|
lock (ByondLock)
|
|
{
|
|
if (updateStat != ByondStatus.Staged)
|
|
return false;
|
|
updateStat = ByondStatus.Updating;
|
|
}
|
|
try
|
|
{
|
|
var rbd = RelativePath(ByondDirectory);
|
|
Program.DeleteDirectory(rbd);
|
|
Directory.Move(RelativePath(StagingDirectoryInner), rbd);
|
|
Program.DeleteDirectory(RelativePath(StagingDirectory));
|
|
lastError = null;
|
|
SendMessage("BYOND: Update completed!", MessageType.DeveloperInfo);
|
|
WriteInfo(String.Format("BYOND update {0} completed!", GetVersion(ByondVersion.Installed)), EventID.BYONDUpdateComplete);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
lastError = e.ToString();
|
|
SendMessage("BYOND: Update failed!", MessageType.DeveloperInfo);
|
|
WriteError("BYOND update failed! Error: " + e.ToString(), EventID.BYONDUpdateFail);
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
lock(ByondLock) {
|
|
updateStat = ByondStatus.Idle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|