Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public override void OnFrameworkInitializationCompleted()
var services = collection.BuildServiceProvider();

var vm = services.GetRequiredService<MainWindowViewModel>();
var serverControl = services.GetRequiredService<ServerControlViewModel>();

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
Expand Down
30 changes: 13 additions & 17 deletions Assets/Templates/httpd.conf.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
#

# ThreadsPerChild: constant number of worker threads in the server process
# MaxRequestsPerChild: maximum number of requests a server process serves
# MaxConnectionsPerChild: maximum number of requests a server process serves
ThreadsPerChild 250
MaxRequestsPerChild 0
MaxConnectionsPerChild 0

#
# ServerRoot: The top of the directory tree under which the server's
Expand Down Expand Up @@ -89,11 +89,13 @@ LoadModule asis_module modules/mod_asis.so
LoadModule auth_basic_module modules/mod_auth_basic.so
#LoadModule auth_digest_module modules/mod_auth_digest.so
#LoadModule authn_anon_module modules/mod_authn_anon.so
LoadModule authn_core_module modules/mod_authn_core.so
#LoadModule authn_dbm_module modules/mod_authn_dbm.so
LoadModule authn_default_module modules/mod_authn_default.so
#LoadModule authn_default_module modules/mod_authn_default.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authz_core_module modules/mod_authz_core.so
#LoadModule authz_dbm_module modules/mod_authz_dbm.so
LoadModule authz_default_module modules/mod_authz_default.so
#LoadModule authz_default_module modules/mod_authz_default.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
Expand Down Expand Up @@ -131,7 +133,7 @@ LoadModule userdir_module modules/mod_userdir.so
#LoadModule usertrack_module modules/mod_usertrack.so
#LoadModule vhost_alias_module modules/mod_vhost_alias.so
#LoadModule ssl_module modules/mod_ssl.so
LoadModule php5_module "{{ server_dir }}/php/php5apache2_2.dll"
LoadModule php7_module "{{ server_dir }}/php/php7apache2_4.dll"

# 'Main' server configuration
#
Expand Down Expand Up @@ -180,9 +182,7 @@ DocumentRoot "{{ apache_docroot }}"
<Directory />
Options FollowSymLinks
AllowOverride All
Order deny,allow
Deny from all
Satisfy all
Require all denied
</Directory>

#
Expand Down Expand Up @@ -223,8 +223,7 @@ DocumentRoot "{{ apache_docroot }}"
#
# Controls who can get stuff from this server.
#
Order allow,deny
Allow from all
Require all granted

</Directory>

Expand All @@ -241,8 +240,7 @@ DocumentRoot "{{ apache_docroot }}"
# viewed by Web clients.
#
<FilesMatch "^\.ht">
Order allow,deny
Deny from all
Require all denied
</FilesMatch>

#
Expand Down Expand Up @@ -331,15 +329,13 @@ LogLevel warn
Options Includes ExecCGI
AddHandler server-parsed .shtml
AddHandler cgi-script .cgi .pl
Order allow,deny
Allow from all
Require all granted
</Directory>

<Directory "{{ apache_base }}/admin/phpmyadmin">
Options Indexes MultiViews
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>

#
Expand Down Expand Up @@ -389,7 +385,7 @@ LogLevel warn
# keep browsers from trying to display binary files as though they are
# text.
#
DefaultType text/plain
# DefaultType text/plain

<IfModule mime_module>
#
Expand Down
10 changes: 5 additions & 5 deletions Assets/Templates/my.ini.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
basedir="{{ server_dir }}\\MySQL"
bind-address={{ mysql_ip }}
datadir="{{ mysql_datadir }}"
default-character-set=latin1
default-collation=latin1_general_ci
#default-character-set=latin1
#default-collation=latin1_general_ci
log-error="{{ tmp_dir }}\\log.err"
pid-file="{{ tmp_dir }}\\mysql.pid"
#slow query log#=
Expand All @@ -15,10 +15,10 @@ port={{ mysql_port }}
innodb_data_home_dir = "{{ mysql_datadir }}"
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = "{{ mysql_datadir }}"
innodb_log_arch_dir = "{{ mysql_datadir }}"
#innodb_log_arch_dir = "{{ mysql_datadir }}"
innodb_buffer_pool_size = 256M
innodb_additional_mem_pool_size = 20M
innodb_log_file_size = 64M
#innodb_additional_mem_pool_size = 20M
#innodb_log_file_size = 64M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
Expand Down
14 changes: 8 additions & 6 deletions Assets/Templates/php.ini.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ implicit_flush = Off
; with future versions of the language (you will receive a warning each time
; you use this feature, and the argument will be passed by value instead of by
; reference).
allow_call_time_pass_reference = On
;; TODO: Make sure functions work, I guess...
; allow_call_time_pass_reference = On

; Safe Mode
;
Expand Down Expand Up @@ -307,7 +308,7 @@ variables_order = "EGPCS"
; You should do your best to write your scripts so that they do not require
; register_globals to be on; Using form variables as globals can easily lead
; to possible security problems, if the code is not very well thought of.
register_globals = On
; register_globals = On

; This directive tells PHP whether to declare the argv&argc variables (that
; would contain the GET information). If you don't use these variables, you
Expand All @@ -324,7 +325,7 @@ gpc_order = "GPC"
;

; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = On
; magic_quotes_gpc = On

; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
magic_quotes_runtime = Off
Expand Down Expand Up @@ -473,7 +474,7 @@ extension=php_mbstring.dll
;extension=php_oci8.dll
extension=php_openssl.dll
;extension=php_oracle.dll
extension=php_pdflib.dll
;extension=php_pdflib.dll
;extension=php_pgsql.dll
;extension=php_printer.dll
;extension=php_shmop.dll
Expand All @@ -488,11 +489,12 @@ extension=php_pdflib.dll
;extension=php_pdo.dll
;extension=php_sqlite.dll
;extension=php_mysql_libmysql.dll
extension=php_mysqli_libmysql.dll
;extension=php_mysqli_libmysql.dll
extension=php_mysqli.dll
;extension=php_pdo_sqlite.dll
;extension=php_pdo_pgsql.dll
;extension=php_pdo_mysql.dll
extension=ixed.5.3ts.win
;extension=ixed.5.3ts.win


;;;;;;;;;;;;;;;;;;;
Expand Down
4 changes: 2 additions & 2 deletions Server/MysqlAdmin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace BLIS_NG.Server;
/// Process wrapper class for running mysqladmin.exe.
/// Does not run like other processes since it will not continually operate.
/// </summary>
public class MySqlAdmin(ILogger<MySqlAdmin> logger) : BaseProcess(nameof(MySqlAdmin), logger)
public class MySqlAdmin(ILogger<MySqlAdmin> logger) : BaseProcess(nameof(MySqlAdmin), logger, singleton: false)
{
private static readonly string MysqlAdminPath = Path.Combine(
ConfigurationFile.SERVER_BASE_DIR, "mysql", "bin", "mysqladmin.exe"
ConfigurationFile.SERVER_BASE_DIR, "mysql", "bin", "mysqladmin.exe"
);

private readonly ILogger<MySqlAdmin> logger = logger;
Expand Down
55 changes: 54 additions & 1 deletion Server/MysqlServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace BLIS_NG.Server;

public class MySqlServer(ILogger<MySqlServer> logger, MySqlIni mySqlIni, MySqlAdmin mySqlAdmin) : BaseProcess(nameof(MySqlServer), logger)
public class MySqlServer(ILogger<MySqlServer> logger, MySqlIni mySqlIni, MySqlAdmin mySqlAdmin, MySqlUpgrade mySqlUpgrade) : BaseProcess(nameof(MySqlServer), logger)
{
private static readonly string MysqldPath = Path.Combine(
ConfigurationFile.SERVER_BASE_DIR, "mysql", "bin", "mysqld.exe");
Expand All @@ -18,15 +18,68 @@ public class MySqlServer(ILogger<MySqlServer> logger, MySqlIni mySqlIni, MySqlAd
private readonly ILogger<MySqlServer> logger = logger;
private readonly MySqlIni mySqlIni = mySqlIni;
private readonly MySqlAdmin mySqlAdmin = mySqlAdmin;
private readonly MySqlUpgrade mySqlUpgrade = mySqlUpgrade;

public async Task<ProcessResult> Run(CancellationToken cancellationToken = default)
{
mySqlIni.Write();

if (UpgradeRequired())
{
var result = await PerformUpgrade(cancellationToken);
if (!result)
{
logger.LogError("MySQL upgrade is required but it could not be completed. Check the logs for details.");
return new ProcessResult(-1);
}
}

return await Execute(MysqldPath, Arguments, null, (stdout) => logger.LogInformation("{StdOut}", stdout), (stderr) => logger.LogWarning("{StdErr}", stderr), cancellationToken);
}

public override async void Stop()
{
await mySqlAdmin.Shutdown();
}

private bool UpgradeRequired()
{
if (!File.Exists(Path.Combine(DataDir, "mysql", "plugin.frm")))
{
logger.LogWarning("mysql/plugin.frm (mysql.plugin table) does not exist. Attempting to upgrade MySQL database.");
return true;
}

return false;
}

private async Task<bool> PerformUpgrade(CancellationToken cancellationToken)
{
// Start MySQL server with the --skip-grant-tables option to disable password authentication to enable upgrading
var args = $"{Arguments} --skip-grant-tables";
var serverTask = Execute(MysqldPath, args, null, (stdout) => logger.LogInformation("{StdOut}", stdout), (stderr) => logger.LogWarning("{StdErr}", stderr), cancellationToken);

bool awake = false;
for (int i = 0; !awake && i < 15; i++)
{
// Give the server a second to wake up...
Thread.Sleep(1000);

awake = await mySqlAdmin.Ping();
if (awake) break;
}

if (!awake)
{
logger.LogError("Could not start MySQL for upgrading.");
return false;
}

await mySqlUpgrade.Run();

await mySqlAdmin.Shutdown();
await serverTask;

return true;
}
}
28 changes: 28 additions & 0 deletions Server/MysqlUpgrade.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BLIS_NG.Config;
using Microsoft.Extensions.Logging;

namespace BLIS_NG.Server;

/// <summary>
/// Process wrapper class for running mysql_upgrade.exe.
/// Does not run like other processes since it will not continually operate.
/// </summary>
public class MySqlUpgrade(ILogger<MySqlUpgrade> logger) : BaseProcess(nameof(MySqlUpgrade), logger, singleton: false)
{
private static readonly string MysqlUpgradePath = Path.Combine(
ConfigurationFile.SERVER_BASE_DIR, "mysql", "bin", "mysql_upgrade.exe");

private readonly ILogger<MySqlUpgrade> logger = logger;
private readonly string Arguments = $"-h {MySqlIni.MYSQL_BIND_ADDRESS} --port {MySqlIni.MYSQL_PORT}";

public override void Stop()
{
// No-op since this process is not long-running.
return;
}

public async Task Run()
{
await Execute(MysqlUpgradePath, Arguments, null, (stdout) => logger.LogInformation("{Message}", stdout), (stderr) => logger.LogWarning("{Message}", stderr));
}
}
7 changes: 4 additions & 3 deletions Server/Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ public record ProcessResult(int ExitCode)
// The initial code for this class was adapted from:
// https://gist.github.com/AlexMAS/276eed492bc989e13dcce7c78b9e179d

public abstract class BaseProcess(string ProcessName, ILogger logger) : IDisposable
public abstract class BaseProcess(string ProcessName, ILogger logger, bool singleton = true) : IDisposable
{
private const int FAILED_TO_LAUNCH = -1;

private readonly ILogger logger = logger;
private readonly string ProcessName = ProcessName;
private readonly bool singleton = singleton;
private Process? process;

public bool IsRunning { get => process != null; }
Expand All @@ -25,7 +26,7 @@ protected async Task<ProcessResult> Execute(string exePath, string arguments, ID
{
var result = new ProcessResult(FAILED_TO_LAUNCH);

if (IsRunning)
if (singleton && IsRunning)
{
logger.LogWarning("Attempted to start {ProcessName} when it is already running.", ProcessName);
return result;
Expand Down Expand Up @@ -105,7 +106,7 @@ protected async Task<ProcessResult> Execute(string exePath, string arguments, ID
var exitWaiter = Task.Run(process.WaitForExit, cancellationToken);
await Task.WhenAll(exitWaiter, outputCloseEvent.Task, errorCloseEvent.Task);

result = new ProcessResult(ExitCode: process.ExitCode);
result = new ProcessResult(ExitCode: process?.ExitCode != null ? process.ExitCode : 1);
}

process = null;
Expand Down
1 change: 1 addition & 0 deletions ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static IServiceCollection AddDependencies(this IServiceCollection service

// Server & utility processes
.AddSingleton<MySqlAdmin>()
.AddSingleton<MySqlUpgrade>()
.AddSingleton<MySqlServer>()
.AddSingleton<Apache2Server>()
.AddSingleton<HealthcheckService>()
Expand Down
12 changes: 9 additions & 3 deletions ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ public MainWindowViewModel(ServerControlViewModel serverControlViewModel)

// Start BLIS on app start
ServerControlViewModel.HandleStartButtonClick();

Thread.Sleep(3000);

WindowState = WindowState.Minimized;
}

public void Shutdown()
{
// Run method synchronously and wait for result.
var awaiter = Task.Run(ServerControlViewModel.HandleStopButtonClick).GetAwaiter();
// Wait a little while for things to shutdown cleanly
Thread.Sleep(5000);
awaiter.GetResult();
}
}
9 changes: 8 additions & 1 deletion ViewModels/ServerControlViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Reactive;
using Avalonia.Controls.ApplicationLifetimes;
using BLIS_NG.Config;
using BLIS_NG.Lib;
using BLIS_NG.Server;
Expand Down Expand Up @@ -145,10 +146,16 @@ private void OpenUrl(Uri url)
{
Process.Start(new ProcessStartInfo { FileName = url.ToString(), UseShellExecute = true });
}
catch(Exception e)
catch (Exception e)
{
logger.LogError(e, "Could not open URL in browser: {Url}", url);
}
}

public void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
{
// Shutdown server when closing
HandleStopButtonClick();
}
}

Loading