Updated database & Implemented Docker support. (unfinished)

This commit is contained in:
netkas 2024-12-30 13:58:47 -05:00
parent c380556255
commit 85a81784f9
25 changed files with 752 additions and 105 deletions

View file

@ -3,6 +3,7 @@
namespace Socialbox\Classes\CliCommands;
use Exception;
use LogLib\Log;
use PDOException;
use Socialbox\Abstracts\CacheLayer;
use Socialbox\Classes\Configuration;
@ -23,7 +24,7 @@
*/
public static function execute(array $args): int
{
if(Configuration::getInstanceConfiguration()->isEnabled() === false && !isset($args['force']))
if(Configuration::getInstanceConfiguration()->isEnabled() === false && !isset($args['force']) && getenv('SB_MODE') !== 'automated')
{
$required_configurations = [
'database.host', 'database.port', 'database.username', 'database.password', 'database.name',
@ -47,9 +48,170 @@
Logger::getLogger()->info(' configlib --conf socialbox -e nano');
Logger::getLogger()->info('Or manually at:');
Logger::getLogger()->info(sprintf(' %s', Configuration::getConfigurationLib()->getPath()));
Logger::getLogger()->info('Automated Setup Procedure is done using environment variables:');
Logger::getLogger()->info(' - SB_MODE=automated');
Logger::getLogger()->info(' - SB_INSTANCE_DOMAIN=example.com => The Domain Name');
Logger::getLogger()->info(' - SB_INSTANCE_RPC_ENDPOINT=http://localhost => The RPC Endpoint, must be publicly accessible');
Logger::getLogger()->info(' - SB_DATABASE_HOST=localhost => The MySQL Host');
Logger::getLogger()->info(' - SB_DATABASE_PORT=3306 => The MySQL Port');
Logger::getLogger()->info(' - SB_DATABASE_USER=root => The MySQL Username');
Logger::getLogger()->info(' - SB_DATABASE_PASSWORD=pass => The MySQL Password');
Logger::getLogger()->info(' - SB_DATABASE_DATABASE=socialbox => The MySQL Database');
Logger::getLogger()->info(' - SB_CACHE_ENGINE=redis => The Cache engine to use, supports redis, memcached or null');
Logger::getLogger()->info(' - SB_CACHE_HOST=localhost => The Cache Host');
Logger::getLogger()->info(' - SB_CACHE_PORT=6379 => The Cache Port');
Logger::getLogger()->info(' - SB_CACHE_PASSWORD=pass => The Cache Password');
Logger::getLogger()->info(' - SB_CACHE_DATABASE=0 => The Cache Database');
Logger::getLogger()->info(' - SB_STORAGE_PATH=/etc/socialbox => The Storage Path');
Logger::getLogger()->info('Anything omitted will be null or empty in the configuration');
return 1;
}
// Overwrite the configuration if the automated setup procedure is detected
// This is useful for CI/CD pipelines & Docker
if(getenv('SB_MODE') === 'automated')
{
Logger::getLogger()->info('Automated Setup Procedure is detected');
if(getenv('SB_INSTANCE_DOMAIN') !== false)
{
Configuration::getConfigurationLib()->set('instance.domain', getenv('SB_INSTANCE_DOMAIN'));
Logger::getLogger()->info('Set instance.domain to ' . getenv('SB_INSTANCE_DOMAIN'));
}
else
{
Logger::getLogger()->warning('instance.domain is required but was not set, expected SB_INSTANCE_DOMAIN environment variable');
}
if(getenv('SB_INSTANCE_RPC_ENDPOINT') !== false)
{
Configuration::getConfigurationLib()->set('instance.rpc_endpoint', getenv('SB_INSTANCE_RPC_ENDPOINT'));
Logger::getLogger()->info('Set instance.rpc_endpoint to ' . getenv('SB_INSTANCE_RPC_ENDPOINT'));
}
else
{
Logger::getLogger()->warning('instance.rpc_endpoint is required but was not set, expected SB_INSTANCE_RPC_ENDPOINT environment variable');
Configuration::getConfigurationLib()->set('instance.rpc_endpoint', 'http://127.0.0.0/');
Logger::getLogger()->info('Set instance.rpc_endpoint to http://127.0.0.0/');
}
if(getenv('SB_STORAGE_PATH') !== false)
{
Configuration::getConfigurationLib()->set('storage.path', getenv('SB_STORAGE_PATH'));
Logger::getLogger()->info('Set storage.path to ' . getenv('SB_STORAGE_PATH'));
}
else
{
Configuration::getConfigurationLib()->set('storage.path', '/etc/socialbox');
Logger::getLogger()->info('storage.path was not set, defaulting to /etc/socialbox');
}
if(getenv('SB_DATABASE_HOST') !== false)
{
Configuration::getConfigurationLib()->set('database.host', getenv('SB_DATABASE_HOST'));
Logger::getLogger()->info('Set database.host to ' . getenv('SB_DATABASE_HOST'));
}
else
{
Logger::getLogger()->warning('database.host is required but was not set, expected SB_DATABASE_HOST environment variable');
}
if(getenv('SB_DATABASE_PORT') !== false)
{
Configuration::getConfigurationLib()->set('database.port', getenv('SB_DATABASE_PORT'));
Logger::getLogger()->info('Set database.port to ' . getenv('SB_DATABASE_PORT'));
}
if(getenv('SB_DATABASE_USERNAME') !== false)
{
Configuration::getConfigurationLib()->set('database.username', getenv('SB_DATABASE_USERNAME'));
Logger::getLogger()->info('Set database.username to ' . getenv('SB_DATABASE_USERNAME'));
}
else
{
Logger::getLogger()->warning('database.username is required but was not set, expected SB_DATABASE_USERNAME environment variable');
}
if(getenv('SB_DATABASE_PASSWORD') !== false)
{
Configuration::getConfigurationLib()->set('database.password', getenv('SB_DATABASE_PASSWORD'));
Logger::getLogger()->info('Set database.password to ' . getenv('SB_DATABASE_PASSWORD'));
}
else
{
Logger::getLogger()->warning('database.password is required but was not set, expected SB_DATABASE_PASSWORD environment variable');
}
if(getenv('SB_DATABASE_NAME') !== false)
{
Configuration::getConfigurationLib()->set('database.name', getenv('SB_DATABASE_NAME'));
Logger::getLogger()->info('Set database.name to ' . getenv('SB_DATABASE_NAME'));
}
else
{
Logger::getLogger()->warning('database.name is required but was not set, expected SB_DATABASE_NAME environment variable');
}
if(getenv('SB_CACHE_ENABLED') !== false)
{
Configuration::getConfigurationLib()->set('cache.enabled', true);
Logger::getLogger()->info('Set cache.engine to true');
}
else
{
Configuration::getConfigurationLib()->set('cache.enabled', false);
Logger::getLogger()->info('cache.engine is was not set, defaulting to false');
}
if(getenv('SB_CACHE_ENGINE') !== false)
{
Configuration::getConfigurationLib()->set('cache.engine', getenv('SB_CACHE_ENGINE'));
Logger::getLogger()->info('Set cache.engine to ' . getenv('SB_CACHE_ENGINE'));
}
if(getenv('SB_CACHE_HOST') !== false)
{
Configuration::getConfigurationLib()->set('cache.host', getenv('SB_CACHE_HOST'));
Logger::getLogger()->info('Set cache.host to ' . getenv('SB_CACHE_HOST'));
}
elseif(Configuration::getCacheConfiguration()->isEnabled())
{
Logger::getLogger()->warning('cache.host is required but was not set, expected SB_CACHE_HOST environment variable');
}
if(getenv('SB_CACHE_PORT') !== false)
{
Configuration::getConfigurationLib()->set('cache.port', getenv('SB_CACHE_PORT'));
Logger::getLogger()->info('Set cache.port to ' . getenv('SB_CACHE_PORT'));
}
if(getenv('SB_CACHE_PASSWORD') !== false)
{
Configuration::getConfigurationLib()->set('cache.password', getenv('SB_CACHE_PASSWORD'));
Logger::getLogger()->info('Set cache.password to ' . getenv('SB_CACHE_PASSWORD'));
}
elseif(Configuration::getCacheConfiguration()->isEnabled())
{
Logger::getLogger()->warning('cache.password is required but was not set, expected SB_CACHE_PASSWORD environment variable');
}
if(getenv('SB_CACHE_DATABASE') !== false)
{
Configuration::getConfigurationLib()->set('cache.database', getenv('SB_CACHE_DATABASE'));
Logger::getLogger()->info('Set cache.database to ' . getenv('SB_CACHE_DATABASE'));
}
elseif(Configuration::getCacheConfiguration()->isEnabled())
{
Configuration::getConfigurationLib()->set('cache.database', 0);
Logger::getLogger()->info('cache.database defaulting to 0');
}
Configuration::getConfigurationLib()->save(); // Save
Configuration::reload(); // Reload
}
if(Configuration::getInstanceConfiguration()->getDomain() === null)
{
Logger::getLogger()->error('instance.domain is required but was not set');
@ -140,14 +302,24 @@
catch (CryptographyException $e)
{
Logger::getLogger()->error('Failed to generate encryption records due to a cryptography exception', $e);
return 1;
}
catch (DatabaseOperationException $e)
{
Logger::getLogger()->error('Failed to generate encryption records due to a database error', $e);
return 1;
}
// TODO: Create a host peer here?
Logger::getLogger()->info('Socialbox has been initialized successfully');
if(getenv('SB_MODE') === 'automated')
{
Configuration::getConfigurationLib()->set('instance.enabled', true);
Configuration::getConfigurationLib()->save(); // Save
Logger::getLogger()->info('Automated Setup Procedure is complete, requests to the RPC server ' . Configuration::getInstanceConfiguration()->getRpcEndpoint() . ' are now accepted');
}
return 0;
}

View file

@ -80,4 +80,15 @@
{
return $this->name;
}
/**
* Constructs and retrieves the Data Source Name (DSN) string.
*
* @return string The DSN string for the database connection.
*/
public function getDsn(): string
{
return sprintf('mysql:host=%s;dbname=%s;port=%s;charset=utf8mb4', $this->host, $this->name, $this->port);
}
}

View file

@ -1,39 +1,41 @@
<?php
namespace Socialbox\Classes;
namespace Socialbox\Classes;
use PDO;
use PDOException;
use Socialbox\Exceptions\DatabaseOperationException;
use PDO;
use PDOException;
use Socialbox\Classes\Configuration\DatabaseConfiguration;
use Socialbox\Exceptions\DatabaseOperationException;
class Database
{
private static ?PDO $instance = null;
/**
* @return PDO
* @throws DatabaseOperationException
*/
public static function getConnection(): PDO
class Database
{
if (self::$instance === null)
{
try
{
$dsn = 'mysql:host=127.0.0.1;dbname=socialbox;port=3306;charset=utf8mb4';
self::$instance = new PDO($dsn, 'root', 'root');
private static ?PDO $instance = null;
// Set some common PDO attributes for better error handling
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch (PDOException $e)
/**
* @return PDO
* @throws DatabaseOperationException
*/
public static function getConnection(): PDO
{
if (self::$instance === null)
{
throw new DatabaseOperationException('Failed to connect to the database', $e);
$dsn = Configuration::getDatabaseConfiguration()->getDsn();
try
{
self::$instance = new PDO($dsn, Configuration::getDatabaseConfiguration()->getUsername(), Configuration::getDatabaseConfiguration()->getPassword());
// Set some common PDO attributes for better error handling
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch (PDOException $e)
{
throw new DatabaseOperationException('Failed to connect to the database using ' . $dsn, $e);
}
}
return self::$instance;
}
return self::$instance;
}
}
}

View file

@ -0,0 +1,20 @@
create table authentication_passwords
(
peer_uuid varchar(36) not null comment 'The Universal Unique Identifier for the peer that is associated with this password record'
primary key comment 'The primary unique index of the peer uuid',
iv mediumtext not null comment 'The Initial Vector of the password record',
encrypted_password mediumtext not null comment 'The encrypted password data',
encrypted_tag mediumtext not null comment 'The encrypted tag of the password record',
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
constraint authentication_passwords_peer_uuid_uindex
unique (peer_uuid) comment 'The primary unique index of the peer uuid',
constraint authentication_passwords_registered_peers_uuid_fk
foreign key (peer_uuid) references registered_peers (uuid)
on update cascade on delete cascade
)
comment 'Table for housing authentication passwords for registered peers';
create index authentication_passwords_updated_index
on authentication_passwords (updated)
comment 'The index of the of the updated column of the record';

View file

@ -0,0 +1,24 @@
create table captcha_images
(
uuid varchar(36) default uuid() not null comment 'The Unique Universal Identifier of the captcha record'
primary key comment 'The Unique index for the UUID column',
peer_uuid varchar(36) not null comment 'The UUID of the peer that is associated with this captcha challenge',
status enum ('UNSOLVED', 'SOLVED') default 'UNSOLVED' not null comment 'The status of the current captcha',
answer varchar(8) null comment 'The current answer for the captcha',
answered timestamp null comment 'The Timestamp for when this captcha was answered',
created timestamp default current_timestamp() not null comment 'The Timestamp for when this captcha record was created',
constraint captchas_peer_uuid_uindex
unique (peer_uuid) comment 'The Primary Unique Index for the peer UUID',
constraint captchas_registered_peers_uuid_fk
foreign key (peer_uuid) references registered_peers (uuid)
on update cascade on delete cascade
);
create index captchas_status_index
on captcha_images (status)
comment 'The Index for the captcha status';
create index captchas_uuid_index
on captcha_images (uuid)
comment 'The Unique index for the UUID column';

View file

@ -0,0 +1,8 @@
create table encryption_records
(
data mediumtext not null comment 'The data column',
iv mediumtext not null comment 'The initialization vector column',
tag mediumtext not null comment 'The authentication tag used to verify if the data was tampered'
)
comment 'Table for housing encryption records for the server';

View file

@ -0,0 +1,35 @@
create table external_sessions
(
uuid varchar(36) default uuid() not null comment 'The UUID of the session for the external connection'
primary key comment 'The Unique Primary Index for the session UUID',
peer_uuid varchar(36) not null comment 'The peer UUID that opened the connection',
session_uuid varchar(36) null comment 'The UUID of the parent session responsible for this external session',
server varchar(255) null comment 'The domain of the remote server that ths external session is authorized for',
created timestamp default current_timestamp() not null comment 'The Timestamp for when this record was created',
last_used timestamp default current_timestamp() not null comment 'The Timestamp for when this session was last used',
constraint external_sessions_uuid_uindex
unique (uuid) comment 'The Unique Primary Index for the session UUID',
constraint external_sessions_registered_peers_uuid_fk
foreign key (peer_uuid) references registered_peers (uuid)
on update cascade on delete cascade,
constraint external_sessions_sessions_uuid_fk
foreign key (session_uuid) references sessions (uuid)
)
comment 'Table for housing external sessions from local to remote servers';
create index external_sessions_created_index
on external_sessions (created)
comment 'The Index for the created column';
create index external_sessions_last_used_index
on external_sessions (last_used)
comment 'The inex for the last used column';
create index external_sessions_peer_uuid_index
on external_sessions (peer_uuid)
comment 'The Index for the peer UUID';
create index external_sessions_session_uuid_index
on external_sessions (session_uuid)
comment 'The index for the original session uuid';

View file

@ -1,18 +0,0 @@
create table password_authentication
(
peer_uuid varchar(36) not null comment 'The Primary unique Index for the peer UUID'
primary key,
value varchar(128) not null comment 'The hash value of the pasword',
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
constraint password_authentication_peer_uuid_uindex
unique (peer_uuid) comment 'The Primary unique Index for the peer UUID',
constraint password_authentication_registered_peers_uuid_fk
foreign key (peer_uuid) references registered_peers (uuid)
on update cascade on delete cascade
)
comment 'Table for housing password authentications associated with peers';
create index password_authentication_updated_index
on password_authentication (updated)
comment 'The Indefor the updated timestamp';

View file

@ -1,20 +1,39 @@
create table registered_peers
(
uuid varchar(36) default uuid() not null comment 'The Primary index for the peer uuid'
uuid varchar(36) default uuid() not null comment 'The Primary index for the peer uuid'
primary key,
username varchar(255) not null comment 'The Unique username associated with the peer',
flags text null comment 'Comma seprted flags associated with the peer',
registered timestamp default current_timestamp() not null comment 'The Timestamp for when the peer was registered on the network',
constraint registered_peers_pk_2
unique (username) comment 'The unique username for the peer',
constraint registered_peers_username_uindex
unique (username) comment 'The unique username for the peer',
username varchar(255) not null comment 'The Unique username associated with the peer',
server varchar(255) default 'host' not null comment 'The server that this peer is registered to',
display_name varchar(255) null comment 'Optional. The Non-Unique Display name of the peer',
display_picture varchar(36) null comment 'The UUID of the display picture that is used, null if none is set.',
flags text null comment 'Comma seprted flags associated with the peer',
enabled tinyint(1) default 0 not null comment 'Boolean column to indicate if this account is Enabled, by default it''s Disabled until the account is verified.',
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this record was last updated',
created timestamp default current_timestamp() not null comment 'The Timestamp for when the peer was registered on the network',
constraint registered_peers_server_username_uindex
unique (server, username) comment 'The Unique Username + Server Index Pair',
constraint registered_peers_uuid_uindex
unique (uuid) comment 'The Primary index for the peer uuid'
)
comment 'Table for housing registered peers under this network';
create index registered_peers_enabled_index
on registered_peers (enabled)
comment 'The index of the enabled column for registered peers';
create index registered_peers_registered_index
on registered_peers (created)
comment 'The Index for the reigstered column of the peer';
create index registered_peers_server_index
on registered_peers (server)
comment 'The Index for the peer''s server';
create index registered_peers_updated_index
on registered_peers (updated)
comment 'The Index for the update column';
create index registered_peers_username_index
on registered_peers (username)
comment 'The index for the registered username';

View file

@ -0,0 +1,12 @@
create table resolved_servers
(
domain varchar(512) not null comment 'The domain name'
primary key comment 'Unique Index for the server domain',
endpoint text not null comment 'The endpoint of the RPC server',
public_key text not null comment 'The Public Key of the server',
updated timestamp default current_timestamp() not null comment 'The TImestamp for when this record was last updated',
constraint resolved_servers_domain_uindex
unique (domain) comment 'Unique Index for the server domain'
)
comment 'A table for housing DNS resolutions';

View file

@ -1,21 +1,26 @@
create table sessions
(
uuid varchar(36) default uuid() not null comment 'The Unique Primary index for the session UUID'
uuid varchar(36) default uuid() not null comment 'The Unique Primary index for the session UUID'
primary key,
authenticated_peer_uuid varchar(36) null comment 'The peer the session is authenticated as, null if the session isn''t authenticated',
public_key blob not null comment 'The client''s public key provided when creating the session',
state enum ('ACTIVE', 'EXPIRED', 'CLOSED') default 'ACTIVE' not null comment 'The status of the session',
created timestamp default current_timestamp() not null comment 'The Timestamp for when the session was last created',
last_request timestamp null comment 'The Timestamp for when the last request was made using this session',
peer_uuid varchar(36) not null comment 'The peer the session is identified as, null if the session isn''t identified as a peer',
client_name varchar(256) not null comment 'The name of the client that is using this session',
client_version varchar(16) not null comment 'The version of the client',
authenticated tinyint(1) default 0 not null comment 'Indicates if the session is currently authenticated as the peer',
public_key text not null comment 'The client''s public key provided when creating the session',
state enum ('AWAITING_DHE', 'ACTIVE', 'CLOSED', 'EXPIRED') default 'AWAITING_DHE' not null comment 'The status of the session',
encryption_key text null comment 'The key used for encryption that is obtained through a DHE',
flags text null comment 'The current flags that is set to the session',
created timestamp default current_timestamp() not null comment 'The Timestamp for when the session was last created',
last_request timestamp null comment 'The Timestamp for when the last request was made using this session',
constraint sessions_uuid_uindex
unique (uuid) comment 'The Unique Primary index for the session UUID',
constraint sessions_registered_peers_uuid_fk
foreign key (authenticated_peer_uuid) references registered_peers (uuid)
foreign key (peer_uuid) references registered_peers (uuid)
on update cascade on delete cascade
);
create index sessions_authenticated_peer_index
on sessions (authenticated_peer_uuid)
on sessions (peer_uuid)
comment 'The Index for the authenticated peer column';
create index sessions_created_index

View file

@ -1,11 +1,12 @@
create table variables
(
name varchar(255) not null comment 'The name of the variable'
primary key comment 'The unique index for the variable name',
value text null comment 'The value of the variable',
`read_only` tinyint(1) default 0 not null comment 'Boolean indicator if the variable is read only',
created timestamp default current_timestamp() not null comment 'The Timestamp for when this record was created',
updated timestamp null comment 'The Timestamp for when this record was last updated',
name varchar(255) not null comment 'The unique index for the variable name'
primary key,
value text null comment 'The value of the variable',
read_only tinyint(1) default 0 not null comment 'Boolean indicator if the variable is read only',
created timestamp default current_timestamp() not null comment 'The Timestamp for when this record was created',
updated timestamp null comment 'The Timestamp for when this record was last updated',
constraint variables_name_uindex
unique (name) comment 'The unique index for the variable name'
);
);

View file

@ -1,38 +1,44 @@
<?php
namespace Socialbox\Enums;
namespace Socialbox\Enums;
enum DatabaseObjects : string
{
case PASSWORD_AUTHENTICATION = 'password_authentication.sql';
case REGISTERED_PEERS = 'registered_peers.sql';
case SESSIONS = 'sessions.sql';
case VARIABLES = 'variables.sql';
/**
* Returns the priority of the database object
*
* @return int The priority of the database object
*/
public function getPriority(): int
enum DatabaseObjects : string
{
return match ($this)
case VARIABLES = 'variables.sql';
case ENCRYPTION_RECORDS = 'encryption_records.sql';
case RESOLVED_SERVERS = 'resolved_servers.sql';
case REGISTERED_PEERS = 'registered_peers.sql';
case AUTHENTICATION_PASSWORDS = 'authentication_passwords.sql';
case CAPTCHA_IMAGES = 'captcha_images.sql';
case SESSIONS = 'sessions.sql';
case EXTERNAL_SESSIONS = 'external_sessions.sql';
/**
* Returns the priority of the database object
*
* @return int The priority of the database object
*/
public function getPriority(): int
{
self::VARIABLES => 0,
self::REGISTERED_PEERS => 1,
self::PASSWORD_AUTHENTICATION, self::SESSIONS => 2,
};
}
return match ($this)
{
self::VARIABLES, self::ENCRYPTION_RECORDS, self::RESOLVED_SERVERS => 0,
self::REGISTERED_PEERS => 1,
self::AUTHENTICATION_PASSWORDS, self::CAPTCHA_IMAGES, self::SESSIONS, self::EXTERNAL_SESSIONS => 2,
};
}
/**
* Returns an array of cases ordered by their priority.
*
* @return array The array of cases sorted by their priority.
*/
public static function casesOrdered(): array
{
$cases = self::cases();
usort($cases, fn($a, $b) => $a->getPriority() <=> $b->getPriority());
return $cases;
/**
* Returns an array of cases ordered by their priority.
*
* @return array The array of cases sorted by their priority.
*/
public static function casesOrdered(): array
{
$cases = self::cases();
usort($cases, fn($a, $b) => $a->getPriority() <=> $b->getPriority());
return $cases;
}
}
}