event service

pull/1/head
Ogoun 5 years ago
parent 68699d2056
commit 2765048aa4

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Text;
using System.Threading;
using ZeroLevel.SqLite;
namespace ZeroLevel.EventServer
{
public class EventRepository
:BaseSqLiteDB
{
private readonly SQLiteConnection _db;
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private readonly string _tableName;
public EventRepository()
{
_tableName = "events";
var path = PrepareDb($"{_tableName}.db");
_db = new SQLiteConnection($"Data Source={path};Version=3;");
_db.Open();
Execute($"CREATE TABLE IF NOT EXISTS {_tableName} (id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT, body BLOB)", _db);
Execute($"CREATE INDEX IF NOT EXISTS key_index ON {_tableName} (key)", _db);
}
}
}

@ -0,0 +1,61 @@
using ZeroLevel.Network;
using ZeroLevel.Services.Applications;
namespace ZeroLevel.EventServer
{
public class EventService
: BaseZeroService
{
public EventService()
{
}
protected override void StartAction()
{
var host = UseHost();
this.AutoregisterInboxes(host);
host.OnConnect += Host_OnConnect;
host.OnDisconnect += Host_OnDisconnect;
}
private void Host_OnDisconnect(ISocketClient obj)
{
Log.Info($"Client '{obj.Endpoint.Address}:{obj.Endpoint.Port}' disconnected");
}
private void Host_OnConnect(IClient obj)
{
Log.Info($"Client '{obj.Socket.Endpoint.Address}:{obj.Socket.Endpoint.Port}' connected");
}
protected override void StopAction()
{
}
#region Inboxes
[ExchangeReplier("onetime")]
public long OneTimeHandler(ISocketClient client, OneTimeEvent e)
{
return 0;
}
[ExchangeReplier("periodic")]
public long PeriodicHandler(ISocketClient client, PeriodicTimeEvent e)
{
return 0;
}
[ExchangeReplier("eventtrigger")]
public long AfterEventHandler(ISocketClient client, EventAfterEvent e)
{
return 0;
}
[ExchangeReplier("eventstrigger")]
public long AfterEventsHandler(ISocketClient client, EventAfterEvents e)
{
return 0;
}
#endregion
}
}

@ -0,0 +1,8 @@
namespace ZeroLevel.EventServer.Model
{
public abstract class BaseEvent
{
public string ServiceKey { get; set; }
public string Inbox { get; set; }
}
}

@ -0,0 +1,23 @@
namespace ZeroLevel.EventServer.Model
{
public enum Condition
: int
{
/// <summary>
/// В любом случае
/// </summary>
None = 0,
/// <summary>
/// Если хотя бы одно событие успешно обработано
/// </summary>
OneSuccessfull = 1,
/// <summary>
/// Если обработаны все события
/// </summary>
AllSuccessfull = 2,
/// <summary>
/// Если хотя бы одно событие не обработано
/// </summary>
AnyFault = 3
}
}

@ -0,0 +1,10 @@
namespace ZeroLevel.EventServer.Model
{
public class EventAfterEvent
: BaseEvent
{
public long EventId { get; set; }
public Condition Confition { get; set; }
}
}

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace ZeroLevel.EventServer.Model
{
public class EventAfterEvents
: BaseEvent
{
public IEnumerable<long> EventIds { get; set; }
public Condition Confition { get; set; }
}
}

@ -0,0 +1,13 @@
namespace ZeroLevel.EventServer
{
public class EventInfoRecord
{
public long EventId { get; set; }
public string ServiceKey { get; set; }
// OR
public string ServiceEndpoint { get; set; }
public string Inbox { get; set; }
}
}

@ -0,0 +1,10 @@
namespace ZeroLevel.EventServer.Model
{
public class EventResult
{
public long EventId;
public EventResultState State;
public long StartTimestamp;
public long EndTimestamp;
}
}

@ -0,0 +1,9 @@
namespace ZeroLevel.EventServer.Model
{
public enum EventResultState
{
InProgress,
Success,
Unsuccess
}
}

@ -0,0 +1,11 @@
namespace ZeroLevel.EventServer.Model
{
public enum EventType
: int
{
OneType = 0,
Periodic = 1,
EventTrigger = 2,
EventsTrigger = 3
}
}

@ -0,0 +1,10 @@
using System;
namespace ZeroLevel.EventServer.Model
{
public class OneTimeEvent
: BaseEvent
{
public TimeSpan Period { get; set; }
}
}

@ -0,0 +1,10 @@
using System;
namespace ZeroLevel.EventServer.Model
{
public class PeriodicTimeEvent
: BaseEvent
{
public TimeSpan Period { get; set; }
}
}

@ -0,0 +1,15 @@
namespace ZeroLevel.EventServer
{
class Program
{
static void Main(string[] args)
{
Bootstrap.Startup<EventService>(args, configuration: () => Configuration.ReadOrEmptySetFromIniFile("config.ini"))
.EnableConsoleLog()
.UseDiscovery()
.Run()
.WaitWhileStatus(ZeroServiceStatus.Running);
Bootstrap.Shutdown();
}
}
}

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.112" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ZeroLevel.SqLite\ZeroLevel.SqLite.csproj" />
<ProjectReference Include="..\ZeroLevel\ZeroLevel.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="config.ini">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

@ -0,0 +1 @@
discovery=localhost:9030

@ -39,7 +39,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroLevel.Logger", "ZeroLev
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroLevel.WPF", "ZeroLevel.WPF\ZeroLevel.WPF.csproj", "{0D70D688-1E21-4E9D-AA49-4D255DF27D8D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroLevel.WPF", "ZeroLevel.WPF\ZeroLevel.WPF.csproj", "{0D70D688-1E21-4E9D-AA49-4D255DF27D8D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZeroLevel.SqLite", "ZeroLevel.SqLite\ZeroLevel.SqLite.csproj", "{5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroLevel.SqLite", "ZeroLevel.SqLite\ZeroLevel.SqLite.csproj", "{5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZeroLevel.EventServer", "ZeroLevel.EventServer\ZeroLevel.EventServer.csproj", "{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -255,6 +257,18 @@ Global
{5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x64.Build.0 = Release|Any CPU {5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x64.Build.0 = Release|Any CPU
{5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x86.ActiveCfg = Release|Any CPU {5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x86.ActiveCfg = Release|Any CPU
{5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x86.Build.0 = Release|Any CPU {5B545DD6-8573-4CDD-B32D-9B0AA2AC2F9A}.Release|x86.Build.0 = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|x64.ActiveCfg = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|x64.Build.0 = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|x86.ActiveCfg = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Debug|x86.Build.0 = Debug|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|Any CPU.Build.0 = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|x64.ActiveCfg = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|x64.Build.0 = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|x86.ActiveCfg = Release|Any CPU
{F8116106-48FD-4F1B-A1D2-63FE81DAFD8E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

@ -133,34 +133,37 @@ namespace ZeroLevel.Services.Applications
#region Network #region Network
public void UseDiscovery() public bool UseDiscovery()
{ {
if (_state == ZeroServiceStatus.Running if (_state == ZeroServiceStatus.Running
|| _state == ZeroServiceStatus.Initialized) || _state == ZeroServiceStatus.Initialized)
{ {
ReadServiceInfo(); ReadServiceInfo();
_exhange.UseDiscovery(); return _exhange.UseDiscovery();
} }
return false;
} }
public void UseDiscovery(string endpoint) public bool UseDiscovery(string endpoint)
{ {
if (_state == ZeroServiceStatus.Running if (_state == ZeroServiceStatus.Running
|| _state == ZeroServiceStatus.Initialized) || _state == ZeroServiceStatus.Initialized)
{ {
ReadServiceInfo(); ReadServiceInfo();
_exhange.UseDiscovery(endpoint); return _exhange.UseDiscovery(endpoint);
} }
return false;
} }
public void UseDiscovery(IPEndPoint endpoint) public bool UseDiscovery(IPEndPoint endpoint)
{ {
if (_state == ZeroServiceStatus.Running if (_state == ZeroServiceStatus.Running
|| _state == ZeroServiceStatus.Initialized) || _state == ZeroServiceStatus.Initialized)
{ {
ReadServiceInfo(); ReadServiceInfo();
_exhange.UseDiscovery(endpoint); return _exhange.UseDiscovery(endpoint);
} }
return false;
} }
public IRouter UseHost() public IRouter UseHost()

@ -2,8 +2,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection;
using ZeroLevel.Services.Config; using ZeroLevel.Services.Config;
using ZeroLevel.Services.Config.Implementation; using ZeroLevel.Services.Config.Implementation;
using ZeroLevel.Services.Reflection; using ZeroLevel.Services.Reflection;
@ -137,52 +135,257 @@ namespace ZeroLevel
/// Creating a configuration from the AppSettings section of the app.config or web.config file /// Creating a configuration from the AppSettings section of the app.config or web.config file
/// </summary> /// </summary>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfiguration ReadFromApplicationConfig() { return new ApplicationConfigReader().ReadConfiguration(); } public static IConfiguration ReadFromApplicationConfig()
{
try
{
return new ApplicationConfigReader().ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadFromApplicationConfig] Can't read app.config file");
throw;
}
}
public static IConfiguration ReadOrEmptyFromApplicationConfig()
{
try
{
return new ApplicationConfigReader().ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptyFromApplicationConfig] Can't read app.config file");
}
return _empty;
}
/// <summary> /// <summary>
/// Creating a configuration from the AppSettings section of the app.config file or web.config, is supplemented by the 'ConnectionStrings' section /// Creating a configuration from the AppSettings section of the app.config file or web.config, is supplemented by the 'ConnectionStrings' section
/// </summary> /// </summary>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfigurationSet ReadSetFromApplicationConfig() { return new ApplicationConfigReader().ReadConfigurationSet(); } public static IConfigurationSet ReadSetFromApplicationConfig()
{
try
{
return new ApplicationConfigReader().ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadSetFromApplicationConfig] Can't read app.config file");
throw;
}
}
public static IConfigurationSet ReadOrEmptySetFromApplicationConfig()
{
try
{
return new ApplicationConfigReader().ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptySetFromApplicationConfig] Can't read app.config file");
}
return _emptySet;
}
/// <summary> /// <summary>
/// Creating a configuration from the AppSettings section of the app.config or web.config file /// Creating a configuration from the AppSettings section of the app.config or web.config file
/// </summary> /// </summary>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfiguration ReadFromApplicationConfig(string configFilePath) { return new ApplicationConfigReader(configFilePath).ReadConfiguration(); } public static IConfiguration ReadFromApplicationConfig(string configFilePath)
{
try
{
return new ApplicationConfigReader(configFilePath).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadFromApplicationConfig] Can't read config file '{configFilePath}'");
throw;
}
}
public static IConfiguration ReadOrEmptyFromApplicationConfig(string configFilePath)
{
try
{
return new ApplicationConfigReader(configFilePath).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptyFromApplicationConfig] Can't read config file '{configFilePath}'");
}
return _empty;
}
/// <summary> /// <summary>
/// Creating a configuration from the AppSettings section of the app.config file or web.config, is supplemented by the 'ConnectionStrings' section /// Creating a configuration from the AppSettings section of the app.config file or web.config, is supplemented by the 'ConnectionStrings' section
/// </summary> /// </summary>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfigurationSet ReadSetFromApplicationConfig(string configFilePath) { return new ApplicationConfigReader(configFilePath).ReadConfigurationSet(); } public static IConfigurationSet ReadSetFromApplicationConfig(string configFilePath)
{
try
{
return new ApplicationConfigReader(configFilePath).ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadSetFromApplicationConfig] Can't read config file '{configFilePath}'");
throw;
}
}
public static IConfigurationSet ReadOrEmptySetFromApplicationConfig(string configFilePath)
{
try
{
return new ApplicationConfigReader(configFilePath).ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptySetFromApplicationConfig] Can't read config file '{configFilePath}'");
}
return _emptySet;
}
/// <summary> /// <summary>
/// Create configuration from ini file /// Create configuration from ini file
/// </summary> /// </summary>
/// <param name="path">Path to the ini file</param> /// <param name="path">Path to the ini file</param>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfiguration ReadFromIniFile(string path) { return new IniFileReader(path).ReadConfiguration(); } public static IConfiguration ReadFromIniFile(string path)
{
try
{
return new IniFileReader(path).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadFromIniFile] Can't read config file '{path}'");
throw;
}
}
public static IConfiguration ReadOrEmptyFromIniFile(string path)
{
try
{
return new IniFileReader(path).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptyFromIniFile] Can't read config file '{path}'");
}
return _empty;
}
/// <summary> /// <summary>
/// Creating a configuration from an ini file, including sections /// Creating a configuration from an ini file, including sections
/// </summary> /// </summary>
/// <param name="path">Path to the ini file</param> /// <param name="path">Path to the ini file</param>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfigurationSet ReadSetFromIniFile(string path) { return new IniFileReader(path).ReadConfigurationSet(); } public static IConfigurationSet ReadSetFromIniFile(string path)
{
try
{
return new IniFileReader(path).ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadSetFromIniFile] Can't read config file '{path}'");
throw;
}
}
public static IConfigurationSet ReadOrEmptySetFromIniFile(string path)
{
try
{
return new IniFileReader(path).ReadConfigurationSet();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptySetFromIniFile] Can't read config file '{path}'");
}
return _emptySet;
}
/// <summary> /// <summary>
/// Creating configuration from command line parameters /// Creating configuration from command line parameters
/// </summary> /// </summary>
/// <param name="args">Command line parameters</param> /// <param name="args">Command line parameters</param>
/// <returns>Configuration</returns> /// <returns>Configuration</returns>
public static IConfiguration ReadFromCommandLine(string[] args) { return new CommandLineReader(args).ReadConfiguration(); } public static IConfiguration ReadFromCommandLine(string[] args)
{
try
{
return new CommandLineReader(args).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadFromCommandLine] Can't read command line args");
throw;
}
}
public static IConfiguration ReadOrEmptyFromCommandLine(string[] args)
{
try
{
return new CommandLineReader(args).ReadConfiguration();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptyFromCommandLine] Can't read command line args");
}
return _empty;
}
public static IConfigurationSet ReadBinary(IBinaryReader reader) public static IConfiguration ReadFromBinaryReader(IBinaryReader reader)
{ {
return reader.Read<BaseConfigurationSet>(); try
{
return reader.Read<BaseConfiguration>();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadFromBinaryReader] Can't read config from binaryReader");
throw;
}
}
public static IConfiguration ReadOrEmptyFromBinaryReader(IBinaryReader reader)
{
try
{
return reader.Read<BaseConfiguration>();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadOrEmptyFromBinaryReader] Can't read config from binaryReader");
}
return _empty;
} }
public static IConfigurationSet ReadSetFromBinaryReader(IBinaryReader reader)
{
try
{
return reader.Read<BaseConfigurationSet>();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadSetFromBinaryReader] Can't read config from binaryReader");
throw;
}
}
public static IConfigurationSet ReadSetOrEmptyFromBinaryReader(IBinaryReader reader)
{
try
{
return reader.Read<BaseConfigurationSet>();
}
catch (Exception ex)
{
Log.Error(ex, $"[Configuration.ReadSetOrEmptyFromBinaryReader] Can't read config from binaryReader");
}
return _emptySet;
}
#endregion Read configuration #endregion Read configuration
#region Write configuration #region Write configuration

@ -15,9 +15,9 @@ namespace ZeroLevel
ZeroServiceInfo ServiceInfo { get; } ZeroServiceInfo ServiceInfo { get; }
void UseDiscovery(); bool UseDiscovery();
void UseDiscovery(string url); bool UseDiscovery(string url);
void UseDiscovery(IPEndPoint endpoint); bool UseDiscovery(IPEndPoint endpoint);
IRouter UseHost(); IRouter UseHost();
IRouter UseHost(int port); IRouter UseHost(int port);

@ -6,9 +6,9 @@ namespace ZeroLevel.Network
public interface IExchange public interface IExchange
: IClientSet, IDisposable : IClientSet, IDisposable
{ {
void UseDiscovery(); bool UseDiscovery();
void UseDiscovery(string endpoint); bool UseDiscovery(string endpoint);
void UseDiscovery(IPEndPoint endpoint); bool UseDiscovery(IPEndPoint endpoint);
IRouter UseHost(); IRouter UseHost();
IRouter UseHost(int port); IRouter UseHost(int port);

@ -431,45 +431,31 @@ namespace ZeroLevel.Network
private static TimeSpan _update_discovery_table_period = TimeSpan.FromSeconds(15); private static TimeSpan _update_discovery_table_period = TimeSpan.FromSeconds(15);
private static TimeSpan _register_in_discovery_table_period = TimeSpan.FromSeconds(15); private static TimeSpan _register_in_discovery_table_period = TimeSpan.FromSeconds(15);
public void UseDiscovery() private bool _UseDiscovery(Func<IPEndPoint> epf)
{ {
if (epf == null) return false;
try try
{ {
var discoveryEndpoint = Configuration.Default.First("discovery"); var ep = epf.Invoke();
_user_aliases.Set(BaseSocket.DISCOVERY_ALIAS, NetUtils.CreateIPEndPoint(discoveryEndpoint)); _user_aliases.Set(BaseSocket.DISCOVERY_ALIAS, ep);
RestartDiscoveryTasks(); RestartDiscoveryTasks();
return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "[Exchange.UseDiscovery]"); Log.Error(ex, $"[Exchange.UseDiscovery]");
} }
return false;
} }
public void UseDiscovery(string discoveryEndpoint) public bool UseDiscovery() =>
{ _UseDiscovery(() => NetUtils.CreateIPEndPoint(Configuration.Default.First("discovery")));
try
{
_user_aliases.Set(BaseSocket.DISCOVERY_ALIAS, NetUtils.CreateIPEndPoint(discoveryEndpoint));
RestartDiscoveryTasks();
}
catch (Exception ex)
{
Log.Error(ex, "[Exchange.UseDiscovery]");
}
}
public void UseDiscovery(IPEndPoint discoveryEndpoint) public bool UseDiscovery(string discoveryEndpoint) =>
{ _UseDiscovery(() => NetUtils.CreateIPEndPoint(discoveryEndpoint));
try
{ public bool UseDiscovery(IPEndPoint discoveryEndpoint) =>
_user_aliases.Set(BaseSocket.DISCOVERY_ALIAS, discoveryEndpoint); _UseDiscovery(() => discoveryEndpoint);
RestartDiscoveryTasks();
}
catch (Exception ex)
{
Log.Error(ex, "[Exchange.UseDiscovery]");
}
}
private void RestartDiscoveryTasks() private void RestartDiscoveryTasks()
{ {

Loading…
Cancel
Save

Powered by TurnKey Linux.