You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
12 KiB
351 lines
12 KiB
using System.Globalization; |
|
using System.Net.Http.Json; |
|
using Bit.Migrator; |
|
using Bit.Setup.Enums; |
|
|
|
namespace Bit.Setup; |
|
|
|
public class Program |
|
{ |
|
private static Context _context; |
|
|
|
public static void Main(string[] args) |
|
{ |
|
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); |
|
|
|
_context = new Context |
|
{ |
|
Args = args |
|
}; |
|
ParseParameters(); |
|
|
|
if (_context.Parameters.ContainsKey("q")) |
|
{ |
|
_context.Quiet = _context.Parameters["q"] == "true" || _context.Parameters["q"] == "1"; |
|
} |
|
if (_context.Parameters.ContainsKey("os")) |
|
{ |
|
_context.HostOS = _context.Parameters["os"]; |
|
} |
|
if (_context.Parameters.ContainsKey("corev")) |
|
{ |
|
_context.CoreVersion = _context.Parameters["corev"]; |
|
} |
|
if (_context.Parameters.ContainsKey("webv")) |
|
{ |
|
_context.WebVersion = _context.Parameters["webv"]; |
|
} |
|
if (_context.Parameters.ContainsKey("keyconnectorv")) |
|
{ |
|
_context.KeyConnectorVersion = _context.Parameters["keyconnectorv"]; |
|
} |
|
if (_context.Parameters.ContainsKey("stub")) |
|
{ |
|
_context.Stub = _context.Parameters["stub"] == "true" || |
|
_context.Parameters["stub"] == "1"; |
|
} |
|
|
|
Helpers.WriteLine(_context); |
|
|
|
if (_context.Parameters.ContainsKey("install")) |
|
{ |
|
Install(); |
|
} |
|
else if (_context.Parameters.ContainsKey("update")) |
|
{ |
|
Update(); |
|
} |
|
else if (_context.Parameters.ContainsKey("printenv")) |
|
{ |
|
PrintEnvironment(); |
|
} |
|
else |
|
{ |
|
Helpers.WriteLine(_context, "No top-level command detected. Exiting..."); |
|
} |
|
} |
|
|
|
private static void Install() |
|
{ |
|
if (_context.Parameters.ContainsKey("letsencrypt")) |
|
{ |
|
_context.Config.SslManagedLetsEncrypt = |
|
_context.Parameters["letsencrypt"].ToLowerInvariant() == "y"; |
|
} |
|
if (_context.Parameters.ContainsKey("domain")) |
|
{ |
|
_context.Install.Domain = _context.Parameters["domain"].ToLowerInvariant(); |
|
} |
|
if (_context.Parameters.ContainsKey("dbname")) |
|
{ |
|
_context.Install.Database = _context.Parameters["dbname"]; |
|
} |
|
|
|
if (_context.Stub) |
|
{ |
|
_context.Install.InstallationId = Guid.Empty; |
|
_context.Install.InstallationKey = "SECRET_INSTALLATION_KEY"; |
|
} |
|
else if (!ValidateInstallation()) |
|
{ |
|
return; |
|
} |
|
|
|
var certBuilder = new CertBuilder(_context); |
|
certBuilder.BuildForInstall(); |
|
|
|
// Set the URL |
|
_context.Config.Url = string.Format("http{0}://{1}", |
|
_context.Config.Ssl ? "s" : string.Empty, _context.Install.Domain); |
|
|
|
var nginxBuilder = new NginxConfigBuilder(_context); |
|
nginxBuilder.BuildForInstaller(); |
|
|
|
var environmentFileBuilder = new EnvironmentFileBuilder(_context); |
|
environmentFileBuilder.BuildForInstaller(); |
|
|
|
var appIdBuilder = new AppIdBuilder(_context); |
|
appIdBuilder.Build(); |
|
|
|
var dockerComposeBuilder = new DockerComposeBuilder(_context); |
|
dockerComposeBuilder.BuildForInstaller(); |
|
|
|
_context.SaveConfiguration(); |
|
|
|
Console.WriteLine("\nInstallation complete"); |
|
|
|
Console.WriteLine("\nIf you need to make additional configuration changes, you can modify\n" + |
|
"the settings in `{0}` and then run:\n{1}", |
|
_context.HostOS == "win" ? ".\\bwdata\\config.yml" : "./bwdata/config.yml", |
|
_context.HostOS == "win" ? "`.\\bitwarden.ps1 -rebuild` or `.\\bitwarden.ps1 -update`" : |
|
"`./bitwarden.sh rebuild` or `./bitwarden.sh update`"); |
|
|
|
Console.WriteLine("\nNext steps, run:"); |
|
if (_context.HostOS == "win") |
|
{ |
|
Console.WriteLine("`.\\bitwarden.ps1 -start`"); |
|
} |
|
else |
|
{ |
|
Console.WriteLine("`./bitwarden.sh start`"); |
|
} |
|
Console.WriteLine(string.Empty); |
|
} |
|
|
|
private static void Update() |
|
{ |
|
// This portion of code checks for multiple certs in the Identity.pfx PKCS12 bag. If found, it generates |
|
// a new cert and bag to replace the old Identity.pfx. This fixes an issue that came up as a result of |
|
// moving the project to .NET 5. |
|
_context.Install.IdentityCertPassword = Helpers.GetValueFromEnvFile("global", "globalSettings__identityServer__certificatePassword"); |
|
var certCountString = Helpers.Exec("openssl pkcs12 -nokeys -info -in /bitwarden/identity/identity.pfx " + |
|
$"-passin pass:{_context.Install.IdentityCertPassword} 2> /dev/null | grep -c \"\\-----BEGIN CERTIFICATE----\"", true); |
|
if (int.TryParse(certCountString, out var certCount) && certCount > 1) |
|
{ |
|
// Extract key from identity.pfx |
|
Helpers.Exec("openssl pkcs12 -in /bitwarden/identity/identity.pfx -nocerts -nodes -out identity.key " + |
|
$"-passin pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1"); |
|
// Extract certificate from identity.pfx |
|
Helpers.Exec("openssl pkcs12 -in /bitwarden/identity/identity.pfx -clcerts -nokeys -out identity.crt " + |
|
$"-passin pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1"); |
|
// Create new PKCS12 bag with certificate and key |
|
Helpers.Exec("openssl pkcs12 -export -out /bitwarden/identity/identity.pfx -inkey identity.key " + |
|
$"-in identity.crt -passout pass:{_context.Install.IdentityCertPassword} > /dev/null 2>&1"); |
|
} |
|
|
|
if (_context.Parameters.ContainsKey("db")) |
|
{ |
|
MigrateDatabase(); |
|
} |
|
else |
|
{ |
|
RebuildConfigs(); |
|
} |
|
} |
|
|
|
private static void PrintEnvironment() |
|
{ |
|
_context.LoadConfiguration(); |
|
if (!_context.PrintToScreen()) |
|
{ |
|
return; |
|
} |
|
Console.WriteLine("\nBitwarden is up and running!"); |
|
Console.WriteLine("==================================================="); |
|
Console.WriteLine("\nvisit {0}", _context.Config.Url); |
|
Console.Write("to update, run "); |
|
if (_context.HostOS == "win") |
|
{ |
|
Console.Write("`.\\bitwarden.ps1 -updateself` and then `.\\bitwarden.ps1 -update`"); |
|
} |
|
else |
|
{ |
|
Console.Write("`./bitwarden.sh updateself` and then `./bitwarden.sh update`"); |
|
} |
|
Console.WriteLine("\n"); |
|
} |
|
|
|
private static void MigrateDatabase(int attempt = 1) |
|
{ |
|
var vaultConnectionString = Helpers.GetValueFromEnvFile("global", |
|
"globalSettings__sqlServer__connectionString"); |
|
var migrator = new DbMigrator(vaultConnectionString, null); |
|
|
|
var log = false; |
|
|
|
migrator.MigrateMsSqlDatabaseWithRetries(log); |
|
|
|
migrator.MigrateMsSqlDatabaseWithRetries(log, true, MigratorConstants.TransitionMigrationsFolderName); |
|
} |
|
|
|
private static bool ValidateInstallation() |
|
{ |
|
var installationId = string.Empty; |
|
var installationKey = string.Empty; |
|
CloudRegion cloudRegion; |
|
|
|
if (_context.Parameters.ContainsKey("install-id")) |
|
{ |
|
installationId = _context.Parameters["install-id"].ToLowerInvariant(); |
|
} |
|
else |
|
{ |
|
var prompt = "Enter your installation id (get at https://bitwarden.com/host)"; |
|
installationId = Helpers.ReadInput(prompt); |
|
while (string.IsNullOrEmpty(installationId)) |
|
{ |
|
Helpers.WriteError("Invalid input for installation id. Please try again."); |
|
installationId = Helpers.ReadInput(prompt); |
|
} |
|
} |
|
|
|
if (!Guid.TryParse(installationId.Trim(), out var installationidGuid)) |
|
{ |
|
Console.WriteLine("Invalid installation id."); |
|
return false; |
|
} |
|
|
|
if (_context.Parameters.ContainsKey("install-key")) |
|
{ |
|
installationKey = _context.Parameters["install-key"]; |
|
} |
|
else |
|
{ |
|
var prompt = "Enter your installation key"; |
|
installationKey = Helpers.ReadInput(prompt); |
|
while (string.IsNullOrEmpty(installationKey)) |
|
{ |
|
Helpers.WriteError("Invalid input for installation key. Please try again."); |
|
installationKey = Helpers.ReadInput(prompt); |
|
} |
|
} |
|
|
|
if (_context.Parameters.ContainsKey("cloud-region")) |
|
{ |
|
Enum.TryParse(_context.Parameters["cloud-region"], out cloudRegion); |
|
} |
|
else |
|
{ |
|
var prompt = "Enter your region (US/EU) [US]"; |
|
var region = Helpers.ReadInput(prompt); |
|
if (string.IsNullOrEmpty(region)) region = "US"; |
|
|
|
while (!Enum.TryParse(region, out cloudRegion)) |
|
{ |
|
Helpers.WriteError("Invalid input for region. Please try again."); |
|
region = Helpers.ReadInput(prompt); |
|
if (string.IsNullOrEmpty(region)) region = "US"; |
|
} |
|
} |
|
|
|
_context.Install.InstallationId = installationidGuid; |
|
_context.Install.InstallationKey = installationKey; |
|
_context.Install.CloudRegion = cloudRegion; |
|
|
|
try |
|
{ |
|
string url; |
|
switch (cloudRegion) |
|
{ |
|
case CloudRegion.EU: |
|
url = "https://api.bitwarden.eu/installations/"; |
|
break; |
|
case CloudRegion.US: |
|
default: |
|
url = "https://api.bitwarden.com/installations/"; |
|
break; |
|
} |
|
var response = new HttpClient().GetAsync(url + _context.Install.InstallationId).GetAwaiter().GetResult(); |
|
|
|
if (!response.IsSuccessStatusCode) |
|
{ |
|
if (response.StatusCode == System.Net.HttpStatusCode.NotFound) |
|
{ |
|
Console.WriteLine($"Invalid installation id for {cloudRegion.ToString()} region."); |
|
} |
|
else |
|
{ |
|
Console.WriteLine($"Unable to validate installation id for {cloudRegion.ToString()} region."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
var result = response.Content.ReadFromJsonAsync<InstallationValidationResponseModel>().GetAwaiter().GetResult(); |
|
if (!result.Enabled) |
|
{ |
|
Console.WriteLine($"Installation id has been disabled in the {cloudRegion.ToString()} region."); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
catch |
|
{ |
|
Console.WriteLine($"Unable to validate installation id. Problem contacting Bitwarden {cloudRegion.ToString()} server."); |
|
return false; |
|
} |
|
} |
|
|
|
private static void RebuildConfigs() |
|
{ |
|
_context.LoadConfiguration(); |
|
|
|
var environmentFileBuilder = new EnvironmentFileBuilder(_context); |
|
environmentFileBuilder.BuildForUpdater(); |
|
|
|
var certBuilder = new CertBuilder(_context); |
|
certBuilder.BuildForUpdater(); |
|
|
|
var nginxBuilder = new NginxConfigBuilder(_context); |
|
nginxBuilder.BuildForUpdater(); |
|
|
|
var appIdBuilder = new AppIdBuilder(_context); |
|
appIdBuilder.Build(); |
|
|
|
var dockerComposeBuilder = new DockerComposeBuilder(_context); |
|
dockerComposeBuilder.BuildForUpdater(); |
|
|
|
_context.SaveConfiguration(); |
|
Console.WriteLine(string.Empty); |
|
} |
|
|
|
private static void ParseParameters() |
|
{ |
|
_context.Parameters = new Dictionary<string, string>(); |
|
for (var i = 0; i < _context.Args.Length; i = i + 2) |
|
{ |
|
if (!_context.Args[i].StartsWith("-")) |
|
{ |
|
continue; |
|
} |
|
|
|
_context.Parameters.Add(_context.Args[i].Substring(1), _context.Args[i + 1]); |
|
} |
|
} |
|
|
|
class InstallationValidationResponseModel |
|
{ |
|
public bool Enabled { get; init; } |
|
} |
|
}
|
|
|