storj/installer/windows/Storj/CustomAction.cs
paul cannon 43cf036113
installer/windows: don't require ca.key file (#3847)
As noted by @sixcorners at
https://github.com/storj/storj/pull/3819#issuecomment-609025487, the
Windows installer code incorrectly fails if the ca.key file is not
present.

The ca.key file should not be necessary for regular storage node usage,
and in fact we recommend that users move the ca.key file to secure
storage rather than keeping it on the node.

I'm not sure how to run the tests, so I'll get a nod from the SNO Growth
team before merging.

Change-Id: Ib2fe236de5c75165644e880caa827a2a1a034c87
Co-authored-by: Stefan Benten <mail@stefan-benten.de>
2020-04-07 10:02:41 -05:00

262 lines
8.5 KiB
C#

using Microsoft.Deployment.WindowsInstaller;
using System;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Text.RegularExpressions;
namespace Storj
{
public class CustomActions
{
[CustomAction]
public static ActionResult ValidateIdentityDir(Session session)
{
string identityDir = session["IDENTITYDIR"];
try
{
new CustomActionRunner().ValidateIdentityDir(identityDir);
}
catch (ArgumentException e)
{
// Identity dir is invalid
session["STORJ_IDENTITYDIR_VALID"] = e.Message;
return ActionResult.Success;
}
// Identity dir is valid
session["STORJ_IDENTITYDIR_VALID"] = "1";
return ActionResult.Success;
}
[CustomAction]
public static ActionResult ValidateWallet(Session session)
{
string wallet = session["STORJ_WALLET"];
try
{
new CustomActionRunner().ValidateWallet(wallet);
} catch (ArgumentException e)
{
// Wallet is invalid
session["STORJ_WALLET_VALID"] = e.Message;
return ActionResult.Success;
}
// Wallet is valid
session["STORJ_WALLET_VALID"] = "1";
return ActionResult.Success;
}
[CustomAction]
public static ActionResult ValidateStorageDir(Session session)
{
string storageDir = session["STORAGEDIR"];
try
{
new CustomActionRunner().ValidateStorageDir(storageDir);
}
catch (ArgumentException e)
{
// Storage dir is invalid
session["STORJ_STORAGEDIR_VALID"] = e.Message;
return ActionResult.Success;
}
// Storage dir is valid
session["STORJ_STORAGEDIR_VALID"] = "1";
return ActionResult.Success;
}
[CustomAction]
public static ActionResult ValidateStorage(Session session)
{
string storageStr = session["STORJ_STORAGE"];
string storageDir = session["STORAGEDIR"];
try
{
new CustomActionRunner().ValidateStorage(storageStr, storageDir);
}
catch (ArgumentException e)
{
// Allocated Storage is invalid
session["STORJ_STORAGE_VALID"] = e.Message;
return ActionResult.Success;
}
// Allocated Storage value is valid
session["STORJ_STORAGE_VALID"] = "1";
return ActionResult.Success;
}
[CustomAction]
public static ActionResult ExtractInstallDir(Session session)
{
string line = session["STORJ_SERVICE_COMMAND"];
session.Log($"ExtractInstallDir registry value: {line}");
string path = new CustomActionRunner().ExtractInstallDir(line);
session.Log($"ExtractInstallDir extracted path: {path}");
session["STORJ_INSTALLDIR"] = path;
return ActionResult.Success;
}
}
public class CustomActionRunner
{
public const long GB = 1000 * 1000 * 1000;
public const long TB = (long)1000 * 1000 * 1000 * 1000;
public const long MinFreeSpace = 550 * GB; // (500 GB + 10% overhead)
private readonly IFileSystem fs;
public CustomActionRunner() : this(fs: new FileSystem())
{
}
public CustomActionRunner(IFileSystem fs)
{
this.fs = fs;
}
public void ValidateIdentityDir(string identityDir)
{
if (string.IsNullOrEmpty(identityDir))
{
throw new ArgumentException("You must select an identity folder.");
}
if (!fs.Directory.Exists(identityDir))
{
throw new ArgumentException(string.Format("Folder '{0}' does not exist.", identityDir));
}
if (!fs.File.Exists(Path.Combine(identityDir, "ca.cert")))
{
throw new ArgumentException("File 'ca.cert' not found in the selected folder.");
}
if (!fs.File.Exists(Path.Combine(identityDir, "identity.cert")))
{
throw new ArgumentException("File 'identity.cert' not found in the selected folder.");
}
if (!fs.File.Exists(Path.Combine(identityDir, "identity.key")))
{
throw new ArgumentException("File 'identity.key' not found in the selected folder.");
}
}
public void ValidateWallet(string wallet)
{
if (string.IsNullOrEmpty(wallet))
{
throw new ArgumentException("The payout address cannot be empty.");
}
if (!wallet.StartsWith("0x"))
{
throw new ArgumentException("The payout address must start with a '0x' prefix.");
}
// Remove 0x prefix
wallet = wallet.Substring(2);
if (wallet.Length != 40)
{
throw new ArgumentException("The payout address must have 40 characters after the '0x' prefix.");
}
// TODO validate address checksum
}
public void ValidateStorageDir(string storageDir)
{
if (string.IsNullOrEmpty(storageDir))
{
throw new ArgumentException("You must select a storage folder.");
}
IDirectoryInfo dir = fs.DirectoryInfo.FromDirectoryName(storageDir);
IDriveInfo drive = fs.DriveInfo.FromDriveName(dir.Root.FullName);
// TODO: Find a way to calculate the available free space + total size of existing pieces
if (drive.TotalSize < MinFreeSpace)
{
throw new ArgumentException(string.Format("The selected drive '{0}' has only {1:0.##} GB disk size. The minimum required is 550 GB.",
drive.Name, decimal.Divide(drive.TotalSize, GB)));
}
}
public void ValidateStorage(string storageStr, string storageDir)
{
if (string.IsNullOrEmpty(storageStr))
{
throw new ArgumentException("The value cannot be empty.");
}
if (!double.TryParse(storageStr, NumberStyles.Number, CultureInfo.CreateSpecificCulture("en-US"), out double storage))
{
throw new ArgumentException(string.Format("'{0}' is not a valid number.", storageStr));
}
if (storage < 0.5)
{
throw new ArgumentException("The allocated disk space cannot be less than 0.5 TB.");
}
if (string.IsNullOrEmpty(storageDir))
{
throw new ArgumentException("The storage directory cannot be empty");
}
long storagePlusOverhead;
try
{
storagePlusOverhead = Convert.ToInt64(storage * 1.1 * TB);
}
catch (OverflowException)
{
throw new ArgumentException(string.Format("{0} TB is too large value for allocated storage.", storage));
}
IDirectoryInfo dir = fs.DirectoryInfo.FromDirectoryName(storageDir);
IDriveInfo drive = fs.DriveInfo.FromDriveName(dir.Root.FullName);
// TODO: Find a way to calculate the available free space + total size of existing pieces
if (drive.TotalSize < storagePlusOverhead)
{
throw new ArgumentException(string.Format("The disk size ({0:0.##} TB) on the selected drive {1} is less than the allocated disk space plus the 10% overhead ({2:0.##} TB total).",
decimal.Divide(drive.TotalSize, TB), drive.Name, decimal.Divide(storagePlusOverhead, TB)));
}
}
public string ExtractInstallDir(string serviceCmd)
{
if (string.IsNullOrEmpty(serviceCmd))
{
return null;
}
Regex pattern = new Regex(@"--config-dir ""(?<installDir>.*)""");
Match match = pattern.Match(serviceCmd);
string installDir = match.Groups["installDir"].Value;
if (string.IsNullOrEmpty(installDir))
{
return null;
}
return installDir;
}
}
}