From 85a81784f9e3ffee2ca959aafec6c85a838a1faf Mon Sep 17 00:00:00 2001 From: netkas Date: Mon, 30 Dec 2024 13:58:47 -0500 Subject: [PATCH] Updated database & Implemented Docker support. (unfinished) --- .env | 20 ++ .gitignore | 3 +- .idea/sqldialects.xml | 2 +- .idea/webResources.xml | 1 + Dockerfile | 119 ++++++++++++ docker-compose.yml | 98 ++++++++++ entrypoint.sh | 23 +++ nginx.conf | 37 ++++ project.json | 2 +- {examples => public}/index.php | 0 redis.conf | 4 + .../Classes/CliCommands/InitializeCommand.php | 174 +++++++++++++++++- .../Configuration/DatabaseConfiguration.php | 11 ++ src/Socialbox/Classes/Database.php | 60 +++--- .../database/authentication_passwords.sql | 20 ++ .../Resources/database/captcha_images.sql | 24 +++ .../Resources/database/encryption_records.sql | 8 + .../Resources/database/external_sessions.sql | 35 ++++ .../database/password_authentication.sql | 18 -- .../Resources/database/registered_peers.sql | 35 +++- .../Resources/database/resolved_servers.sql | 12 ++ .../Classes/Resources/database/sessions.sql | 21 ++- .../Classes/Resources/database/variables.sql | 15 +- src/Socialbox/Enums/DatabaseObjects.php | 68 +++---- supervisord.conf | 47 +++++ 25 files changed, 752 insertions(+), 105 deletions(-) create mode 100644 .env create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 nginx.conf rename {examples => public}/index.php (100%) create mode 100644 redis.conf create mode 100644 src/Socialbox/Classes/Resources/database/authentication_passwords.sql create mode 100644 src/Socialbox/Classes/Resources/database/captcha_images.sql create mode 100644 src/Socialbox/Classes/Resources/database/encryption_records.sql create mode 100644 src/Socialbox/Classes/Resources/database/external_sessions.sql delete mode 100644 src/Socialbox/Classes/Resources/database/password_authentication.sql create mode 100644 src/Socialbox/Classes/Resources/database/resolved_servers.sql create mode 100644 supervisord.conf diff --git a/.env b/.env new file mode 100644 index 0000000..e6e3e7b --- /dev/null +++ b/.env @@ -0,0 +1,20 @@ +# Socialbox Configuration +LOG_LEVEL=debug +SB_MODE=automated +SB_DOMAIN=localhost +SB_RPC_ENDPOINT=http://127.0.0.0:8085/ + +# MariaDB Configuration +MYSQL_ROOT_PASSWORD=sb_root +MYSQL_DATABASE=socialbox +MYSQL_USER=socialbox +MYSQL_PASSWORD=socialbox + +# Redis Configuration +REDIS_PASSWORD=root + +# Test Configuration, can be ignored. Used for docker-compose-test.yml +SB_ALICE_DOMAIN=localhost +SB_ALICE_RPC_ENDPOINT=http://127.0.0.0:8086/ +SB_BOB_DOMAIN=localhost +SB_BOB_RPC_ENDPOINT=http://127.0.0.0:8087/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 33a3681..e53be25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build /.idea/gbrowser_project.xml -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +/socialbox \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 5975784..bb99828 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,7 +1,7 @@ - + diff --git a/.idea/webResources.xml b/.idea/webResources.xml index 38d7e3c..7990450 100644 --- a/.idea/webResources.xml +++ b/.idea/webResources.xml @@ -7,6 +7,7 @@ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..22628b7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,119 @@ +# ----------------------------------------------------------------------------- +# Dockerfile for PHP 8.3 + FPM with Cron support and Supervisor +# ----------------------------------------------------------------------------- + +# Base image: Official PHP 8.3 with FPM +FROM php:8.3-fpm AS base + +# ----------------------------- Metadata labels ------------------------------ +LABEL maintainer="Netkas " \ + version="1.0" \ + description="Socialbox Docker image based off PHP 8.3 FPM and NCC" \ + application="SocialBox" \ + base_image="php:8.3-fpm" + +# Environment variable for non-interactive installations +ENV DEBIAN_FRONTEND=noninteractive + +# ----------------------------- System Dependencies -------------------------- +# Update system packages and install required dependencies in one step +RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ + git \ + libpq-dev \ + libzip-dev \ + zip \ + make \ + wget \ + gnupg \ + cron \ + supervisor \ + mariadb-client \ + libcurl4-openssl-dev \ + libmemcached-dev \ + redis \ + libgd-dev \ + nginx \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# ----------------------------- PHP Extensions ------------------------------- +# Install PHP extensions and enable additional ones +RUN docker-php-ext-install -j$(nproc) \ + pdo \ + pdo_mysql \ + mysqli \ + gd \ + curl \ + opcache \ + zip \ + pcntl && \ + pecl install redis memcached && \ + docker-php-ext-enable redis memcached + +# ----------------------------- Additional Tools ----------------------------- +# Install Phive (Package Manager for PHAR libraries) and global tools in one step +RUN wget -O /usr/local/bin/phive https://phar.io/releases/phive.phar && \ + wget -O /usr/local/bin/phive.asc https://phar.io/releases/phive.phar.asc && \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 && \ + gpg --verify /usr/local/bin/phive.asc /usr/local/bin/phive && \ + chmod +x /usr/local/bin/phive && \ + phive install phpab --global --trust-gpg-keys 0x2A8299CE842DD38C + +# ----------------------------- Clone and Build NCC -------------------------- +# Clone the NCC repository, build the project, and install it +RUN git clone https://git.n64.cc/nosial/ncc.git && \ + cd ncc && \ + make redist && \ + NCC_DIR=$(find build/ -type d -name "ncc_*" | head -n 1) && \ + if [ -z "$NCC_DIR" ]; then \ + echo "NCC build directory not found"; \ + exit 1; \ + fi && \ + php "$NCC_DIR/INSTALL" --auto && \ + cd .. && rm -rf ncc + +# ----------------------------- Project Build --------------------------------- +# Set build directory and copy pre-needed project files +WORKDIR /tmp/build +COPY . . + +RUN ncc build --config release --build-source --log-level debug && \ + ncc package install --package=build/release/net.nosial.socialbox.ncc --build-source -y --log-level=debug + +# Clean up +RUN rm -rf /tmp/build && rm -rf /var/www/html/* + +# Copy over the required files +COPY nginx.conf /etc/nginx/nginx.conf +COPY public/index.php /var/www/html/index.php +RUN chown -R www-data:www-data /var/www/html && chmod -R 755 /var/www/html + +# ----------------------------- Cron Configuration --------------------------- +RUN echo "*/1 * * * * root for i in {1..12}; do /usr/bin/socialbox process-outgoing; sleep 5; done" > /etc/cron.d/socialbox-process-outgoing && \ + echo "*/1 * * * * root /usr/bin/socialbox session-cleanup" > /etc/cron.d/socialbox-session-cleanup && \ + echo "*/5 * * * * root /usr/bin/socialbox peer-cleanup" > /etc/cron.d/socialbox-peer-cleanup && \ + \ + chmod 0644 /etc/cron.d/socialbox-process-outgoing && \ + chmod 0644 /etc/cron.d/socialbox-session-cleanup && \ + chmod 0644 /etc/cron.d/socialbox-peer-cleanup && \ + \ + crontab /etc/cron.d/socialbox-process-outgoing && \ + crontab /etc/cron.d/socialbox-session-cleanup && \ + crontab /etc/cron.d/socialbox-peer-cleanup + +# ----------------------------- Supervisor Configuration --------------------- +# Copy Supervisor configuration +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# ----------------------------- Cleanup --------------------- +WORKDIR / + +# ----------------------------- Port Exposing --------------------------------- +EXPOSE 8085 + +# ----------------------------- Container Startup ---------------------------- +# Copy over entrypoint script and set it as executable +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Set the entrypoint +ENTRYPOINT ["/usr/bin/bash", "/usr/local/bin/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b67ee76 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,98 @@ +services: + socialbox: + container_name: socialbox + build: + context: . + dockerfile: Dockerfile + ports: + - "8085:8085" + depends_on: + mariadb: + condition: service_healthy + redis: + condition: service_healthy + networks: + - internal_network + restart: unless-stopped + volumes: + - ./socialbox/config:/etc/config + - ./socialbox/logs:/var/log + - ./socialbox/data:/etc/socialbox + environment: + # No need to change these values + LOG_LEVEL: ${LOG_LEVEL:-debug} + CONFIGLIB_PATH: /etc/config + LOGGING_DIRECTORY: /var/log + SB_MODE: automated + SB_STORAGE_PATH: /etc/socialbox + # Change these values to match your environment or update the .env file + SB_INSTANCE_DOMAIN: ${SB_DOMAIN:-localhost} + SB_INSTANCE_RPC_ENDPOINT: ${SB_RPC_ENDPOINT:-http://127.0.0.0:8085/} + SB_DATABASE_HOST: mariadb + SB_DATABASE_USERNAME: ${MYSQL_USER:-socialbox} + SB_DATABASE_PASSWORD: ${MYSQL_PASSWORD:-socialbox} + SB_DATABASE_NAME: ${MYSQL_DATABASE:-socialbox} + SB_CACHE_ENGINE: redis + SB_CACHE_HOST: redis + SB_CACHE_PASSWORD: ${REDIS_PASSWORD:-root} + healthcheck: + test: ["CMD", "curl", "-f", "-H", "Request-Type: ping", "${SB_INSTANCE_DOMAIN-http://127.0.0.0:8085/}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + mariadb: + container_name: socialbox_mariadb + image: mariadb:10.5 + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-sb_root} + MYSQL_DATABASE: ${MYSQL_DATABASE:-socialbox} + MYSQL_USER: ${MYSQL_USER:-socialbox} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-socialbox} + volumes: + - mariadb_data:/var/lib/mysql + networks: + - internal_network + expose: + - "3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "mariadb", "-u", "${MYSQL_USER:-socialbox}", "-p${MYSQL_PASSWORD:-socialbox}"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 30s + + redis: + container_name: socialbox_redis + image: redis:alpine + restart: unless-stopped + command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes + volumes: + - redis_data:/data + - ./redis.conf:/usr/local/etc/redis/redis.conf + networks: + - internal_network + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD:-root} + REDIS_DB: 0 + expose: + - "6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 5s + +volumes: + mariadb_data: + driver: local + redis_data: + driver: local + +networks: + internal_network: + driver: bridge + name: socialbox_network \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..22a954b --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Banner with cool ASCII art +echo "███████╗ ██████╗ ██████╗██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗" +echo "██╔════╝██╔═══██╗██╔════╝██║██╔══██╗██║ ██╔══██╗██╔═══██╗╚██╗██╔╝" +echo "███████╗██║ ██║██║ ██║███████║██║ ██████╔╝██║ ██║ ╚███╔╝ " +echo "╚════██║██║ ██║██║ ██║██╔══██║██║ ██╔══██╗██║ ██║ ██╔██╗ " +echo "███████║╚██████╔╝╚██████╗██║██║ ██║███████╗██████╔╝╚██████╔╝██╔╝ ██╗" +echo "╚══════╝ ╚═════╝ ╚═════╝╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝" + +# Check if the environment variable SB_MODE is set to "automated", if not exit. +if [ "$SB_MODE" != "automated" ]; then + echo "SB_MODE is not set to 'automated', exiting..." + exit 1 +fi + +# Initialize Socialbox +echo "Initializing Socialbox..." +/usr/bin/socialbox init --log-level=${LOG_LEVEL-INFO} + +# Run supervisord, final command +echo "Starting supervisord..." +/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..9909244 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,37 @@ +http { + include mime.types; + default_type application/octet-stream; + + server { + listen 8085; + server_name localhost; + + access_log /var/log/access.log; + error_log /var/log/error.log; + + root /var/www/html; + index index.php; + + # Handle all requests + location / { + try_files $uri $uri/ /index.php?$query_string =503; + autoindex off; + } + + location ~ \.php$ { + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + # Block any .ht* files + location ~ /\.ht { + deny all; + } + } +} + +events { + worker_connections 1024; +} \ No newline at end of file diff --git a/project.json b/project.json index 985bc07..19c0bbc 100644 --- a/project.json +++ b/project.json @@ -93,7 +93,7 @@ "execute": { "working_directory": "%CWD%", "silent": false, - "tty": true, + "tty": false, "timeout": null, "idle_timeout": null, "target": "main" diff --git a/examples/index.php b/public/index.php similarity index 100% rename from examples/index.php rename to public/index.php diff --git a/redis.conf b/redis.conf new file mode 100644 index 0000000..6192ab8 --- /dev/null +++ b/redis.conf @@ -0,0 +1,4 @@ +bind 0.0.0.0 +protected-mode yes +port 6379 +appendonly yes diff --git a/src/Socialbox/Classes/CliCommands/InitializeCommand.php b/src/Socialbox/Classes/CliCommands/InitializeCommand.php index aa3c145..906679d 100644 --- a/src/Socialbox/Classes/CliCommands/InitializeCommand.php +++ b/src/Socialbox/Classes/CliCommands/InitializeCommand.php @@ -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; } diff --git a/src/Socialbox/Classes/Configuration/DatabaseConfiguration.php b/src/Socialbox/Classes/Configuration/DatabaseConfiguration.php index 1093f68..05ff654 100644 --- a/src/Socialbox/Classes/Configuration/DatabaseConfiguration.php +++ b/src/Socialbox/Classes/Configuration/DatabaseConfiguration.php @@ -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); + } } \ No newline at end of file diff --git a/src/Socialbox/Classes/Database.php b/src/Socialbox/Classes/Database.php index 68b3955..93bc800 100644 --- a/src/Socialbox/Classes/Database.php +++ b/src/Socialbox/Classes/Database.php @@ -1,39 +1,41 @@ 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; - } - -} \ No newline at end of file + } \ No newline at end of file diff --git a/src/Socialbox/Classes/Resources/database/authentication_passwords.sql b/src/Socialbox/Classes/Resources/database/authentication_passwords.sql new file mode 100644 index 0000000..7d91842 --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/authentication_passwords.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/captcha_images.sql b/src/Socialbox/Classes/Resources/database/captcha_images.sql new file mode 100644 index 0000000..03336da --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/captcha_images.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/encryption_records.sql b/src/Socialbox/Classes/Resources/database/encryption_records.sql new file mode 100644 index 0000000..77c75bf --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/encryption_records.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/external_sessions.sql b/src/Socialbox/Classes/Resources/database/external_sessions.sql new file mode 100644 index 0000000..96b198f --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/external_sessions.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/password_authentication.sql b/src/Socialbox/Classes/Resources/database/password_authentication.sql deleted file mode 100644 index 90bc79a..0000000 --- a/src/Socialbox/Classes/Resources/database/password_authentication.sql +++ /dev/null @@ -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'; - diff --git a/src/Socialbox/Classes/Resources/database/registered_peers.sql b/src/Socialbox/Classes/Resources/database/registered_peers.sql index 01f9229..d0486b0 100644 --- a/src/Socialbox/Classes/Resources/database/registered_peers.sql +++ b/src/Socialbox/Classes/Resources/database/registered_peers.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/resolved_servers.sql b/src/Socialbox/Classes/Resources/database/resolved_servers.sql new file mode 100644 index 0000000..0aa906f --- /dev/null +++ b/src/Socialbox/Classes/Resources/database/resolved_servers.sql @@ -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'; + diff --git a/src/Socialbox/Classes/Resources/database/sessions.sql b/src/Socialbox/Classes/Resources/database/sessions.sql index 6c247b4..d24aa40 100644 --- a/src/Socialbox/Classes/Resources/database/sessions.sql +++ b/src/Socialbox/Classes/Resources/database/sessions.sql @@ -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 diff --git a/src/Socialbox/Classes/Resources/database/variables.sql b/src/Socialbox/Classes/Resources/database/variables.sql index 29b865d..48dbd01 100644 --- a/src/Socialbox/Classes/Resources/database/variables.sql +++ b/src/Socialbox/Classes/Resources/database/variables.sql @@ -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' -); \ No newline at end of file +); + diff --git a/src/Socialbox/Enums/DatabaseObjects.php b/src/Socialbox/Enums/DatabaseObjects.php index d0037e3..95700af 100644 --- a/src/Socialbox/Enums/DatabaseObjects.php +++ b/src/Socialbox/Enums/DatabaseObjects.php @@ -1,38 +1,44 @@ 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; + } } -} diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..b46bbce --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,47 @@ +[supervisord] +logfile=/var/logd.log +logfile_maxbytes=50MB +logfile_backups=10 +loglevel=info +user=root +pidfile=/var/run/supervisord.pid +umask=022 +nodaemon=true +minfds=1024 +minprocs=200 + +[program:php-fpm] +command=/usr/local/sbin/php-fpm --nodaemonize +autostart=true +autorestart=true +priority=20 +stdout_logfile=/var/log/fpm.log +stderr_logfile=/var/log/fpm_error.log +stdout_logfile_maxbytes=20MB +stdout_logfile_backups=5 +stderr_logfile_maxbytes=20MB +stderr_logfile_backups=5 + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" -c /etc/nginx/nginx.conf +autostart=true +autorestart=true +priority=10 +stdout_logfile=/var/log/nginx.log +stderr_logfile=/var/log/nginx_error.log +stdout_logfile_maxbytes=20MB +stdout_logfile_backups=5 +stderr_logfile_maxbytes=20MB +stderr_logfile_backups=5 + +[program:cron] +command=cron -f -L 15 +autostart=true +autorestart=true +priority=30 +stdout_logfile=/var/log/cron.log +stderr_logfile=/var/log/cron_error.log +stdout_logfile_maxbytes=20MB +stdout_logfile_backups=5 +stderr_logfile_maxbytes=20MB +stderr_logfile_backups=5 \ No newline at end of file