From d3f36e48f7b5faec52782506f956cf7b1a288b72 Mon Sep 17 00:00:00 2001 From: deffcolony <61471128+deffcolony@users.noreply.github.com> Date: Sun, 20 Apr 2025 22:46:12 +0200 Subject: [PATCH] Enhance Docker setup and installation process - Updated Docker Compose configuration for web, PHP, and database services. - Added Redis service with health checks and environment variables. - Improved error handling and user prompts in installation scripts. - Introduced example environment variables for easier configuration. - Updated Nginx and PHP Docker images to latest versions. --- .env.example | 28 +++ compose.yml | 57 +++++-- docker/nginx/Dockerfile | 2 +- docker/php/Dockerfile | 2 +- inc/Data/Driver/RedisCacheDriver.php | 109 +++++++----- install.php | 178 +++++++++++--------- templates/installer/check-requirements.html | 111 ++++++------ templates/installer/config.html | 2 +- 8 files changed, 301 insertions(+), 188 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..122a244f --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# Example environment variables for the vichan imageboard Docker Compose setup. +# Copy this file to `.env` and customize values as needed. +# Do not commit `.env` with sensitive data to version control. + +# General Settings +INSTANCE=0 + +# Web Service (Nginx) Settings +# SSL_CERT_PATH=/path/to/cert.pem +# SSL_KEY_PATH=/path/to/key.pem + +# PHP Service Settings +VICHAN_MYSQL__HOST=db +VICHAN_MYSQL__USER=vichan +VICHAN_MYSQL__NAME=vichan +VICHAN_MYSQL__PASSWORD=vichan! +VICHAN_SECURE_LOGIN_ONLY=0 + +# Redis Settings +VICHAN_REDIS_HOST=redis +VICHAN_REDIS_PORT=6379 +VICHAN_REDIS_PASSWORD=redis! + +# Database Service (MariaDB) Settings +MYSQL_DATABASE=vichan +MYSQL_USER=vichan +MYSQL_PASSWORD=vichan! +MYSQL_ROOT_PASSWORD=vichan!! \ No newline at end of file diff --git a/compose.yml b/compose.yml index 4b87e0b6..0480a33b 100644 --- a/compose.yml +++ b/compose.yml @@ -1,18 +1,22 @@ services: - #nginx webserver + php 8.x web: build: context: . dockerfile: ./docker/nginx/Dockerfile + container_name: vichan_frontend + restart: unless-stopped ports: - "9090:80" +# - "9092:443" # optional for SSL depends_on: - db + - php volumes: - ./local-instances/${INSTANCE:-0}/www:/var/www/html - ./docker/nginx/vichan.conf:/etc/nginx/conf.d/default.conf - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf - ./docker/nginx/proxy.conf:/etc/nginx/conf.d/proxy.conf +# - ./docker/nginx/ssl:/etc/nginx/ssl # optional For SSL links: - php @@ -20,21 +24,48 @@ services: build: context: . dockerfile: ./docker/php/Dockerfile + container_name: vichan_php + restart: unless-stopped volumes: - ./local-instances/${INSTANCE:-0}/www:/var/www - ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf - ./docker/php/jit.ini:/usr/local/etc/php/conf.d/jit.ini - - #MySQL Service - db: - image: mysql:8.0.35 - container_name: db - restart: unless-stopped - tty: true - ports: - - "3306:3306" environment: - MYSQL_DATABASE: vichan - MYSQL_ROOT_PASSWORD: password + VICHAN_MYSQL__HOST: ${VICHAN_MYSQL__HOST} + VICHAN_MYSQL__USER: ${VICHAN_MYSQL__USER} + VICHAN_MYSQL__NAME: ${VICHAN_MYSQL__NAME} + VICHAN_MYSQL__PASSWORD: ${VICHAN_MYSQL__PASSWORD} + VICHAN_SECURE_LOGIN_ONLY: ${VICHAN_SECURE_LOGIN_ONLY} + VICHAN_REDIS_HOST: ${VICHAN_REDIS_HOST} + VICHAN_REDIS_PORT: ${VICHAN_REDIS_PORT} + VICHAN_REDIS_PASSWORD: ${VICHAN_REDIS_PASSWORD} + depends_on: + - db + + db: + image: mariadb:latest + container_name: vichan_mariadb + restart: unless-stopped + ports: + - "9091:3306" + environment: + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} volumes: - - ./local-instances/${INSTANCE:-0}/mysql:/var/lib/mysql + - ./local-instances/${INSTANCE:-0}/db:/var/lib/mysql + + redis: + image: redis:latest + container_name: vichan_redis + restart: unless-stopped + volumes: + - ./local-instances/${INSTANCE:-0}/redis:/data + command: redis-server --requirepass ${VICHAN_REDIS_PASSWORD} + healthcheck: + test: ["CMD", "redis-cli", "-a", "${VICHAN_REDIS_PASSWORD}", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s \ No newline at end of file diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index d9d4bcc4..6ddce075 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.25.3-alpine +FROM nginx:1.27.5-alpine-slim COPY . /code RUN adduser --system www-data \ diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 1882bc9d..c3e855ac 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,7 +1,7 @@ # Based on https://github.com/dead-guru/devichan/blob/master/php-fpm/Dockerfile FROM composer:lts AS composer -FROM php:8.1-fpm-alpine +FROM php:8.3-fpm-alpine RUN apk add --no-cache \ zlib \ diff --git a/inc/Data/Driver/RedisCacheDriver.php b/inc/Data/Driver/RedisCacheDriver.php index 46e602d4..ad193acd 100644 --- a/inc/Data/Driver/RedisCacheDriver.php +++ b/inc/Data/Driver/RedisCacheDriver.php @@ -1,48 +1,73 @@ inner = new \Redis(); - $this->inner->connect($host, $port); - if ($password) { - $this->inner->auth($password); - } - if (!$this->inner->select($database)) { - throw new \RuntimeException('Unable to connect to Redis!'); - } - - $$this->prefix = $prefix; - } - - public function get(string $key): mixed { - $ret = $this->inner->get($this->prefix . $key); - if ($ret === false) { - return null; - } - return \json_decode($ret, true); - } - - public function set(string $key, mixed $value, mixed $expires = false): void { - if ($expires === false) { - $this->inner->set($this->prefix . $key, \json_encode($value)); - } else { - $expires = $expires * 1000; // Seconds to milliseconds. - $this->inner->setex($this->prefix . $key, $expires, \json_encode($value)); - } - } - - public function delete(string $key): void { - $this->inner->del($this->prefix . $key); - } - - public function flush(): void { - $this->inner->flushDB(); - } +// Defines the required methods for any cache driver +interface CacheDriver { + public function get(string $key): mixed; + public function set(string $key, mixed $value, mixed $expires = false): void; + public function delete(string $key): void; + public function flush(): void; +} + +// Handles caching using Redis, a fast in-memory data store +class RedisCacheDriver implements CacheDriver { + private string $prefix; + private \Redis $inner; + + // Sets up the Redis connection + public function __construct(string $prefix, string $host, int $port, ?string $password, int $database) { + $this->inner = new \Redis(); + $this->inner->connect($host, $port); + + if ($password) { + $this->inner->auth($password); + } + + if (!$this->inner->select($database)) { + throw new \RuntimeException('Unable to select Redis database ' . $database); + } + + $this->prefix = $prefix; + } + + // Retrieves a value from the cache by key + public function get(string $key): mixed { + + $ret = $this->inner->get($this->prefix . $key); + if ($ret === false) { + // Return null if the key doesn't exist + return null; + } + + return \json_decode($ret, true); + } + + // Stores a value in the cache with an optional expiration time + public function set(string $key, mixed $value, mixed $expires = false): void { + // Convert the value to JSON for storage + $encodedValue = \json_encode($value); + + if ($expires === false || !is_numeric($expires) || $expires <= 0) { + // Store the value without an expiration + $this->inner->set($this->prefix . $key, $encodedValue); + } else { + // Store the value with an expiration time (in seconds) + $ttl_seconds = (int)$expires; + $this->inner->setex($this->prefix . $key, $ttl_seconds, $encodedValue); + } + } + + // Deletes a specific key from the cache + public function delete(string $key): void { + // Remove the key from Redis + $this->inner->del($this->prefix . $key); + } + + // Clears all data in the current Redis database + public function flush(): void { + $this->inner->flushDB(); + } } diff --git a/install.php b/install.php index 6c8d627d..ec33fdc5 100644 --- a/install.php +++ b/install.php @@ -387,7 +387,7 @@ if (file_exists($config['has_installed'])) { CHANGE `theme` `theme` VARCHAR( 40 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , CHANGE `name` `name` VARCHAR( 40 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL , CHANGE `value` `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL , - DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or eror(db_error()); + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); case 'v0.9.6-dev-10': query("ALTER TABLE `antispam` CHANGE `board` `board` VARCHAR( 58 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;") or error(db_error()); @@ -680,12 +680,12 @@ function create_config_from_array(&$instance_config, &$array, $prefix = '') { session_start(); if ($step == 0) { - // Agreeement - $page['body'] = ' - -
- I have read and understood the agreement. Proceed to installation. -
'; + // Agreement + $page['body'] = ' + ++ +
'; echo Element('page.html', $page); } elseif ($step == 1) { @@ -914,23 +914,35 @@ if ($step == 0) { 'config' => $config, )); } elseif ($step == 2) { + $page['title'] = 'Configuration'; + $sg = new SaltGen(); + $config['cookies']['salt'] = $sg->generate(); + $config['secure_trip_salt'] = $sg->generate(); + $config['secure_password_salt'] = $sg->generate(); + + // Set database configuration from Docker environment variables, leave empty if not found + $config['db'] = array( + 'type' => 'mysql', // Default, required for MySQL + 'server' => getenv('VICHAN_MYSQL__HOST') !== false ? getenv('VICHAN_MYSQL__HOST') : '', + 'database' => getenv('VICHAN_MYSQL__NAME') !== false ? getenv('VICHAN_MYSQL__NAME') : '', + 'user' => getenv('VICHAN_MYSQL__USER') !== false ? getenv('VICHAN_MYSQL__USER') : '', + 'password' => getenv('VICHAN_MYSQL__PASSWORD') !== false ? getenv('VICHAN_MYSQL__PASSWORD') : '', + ); - // Basic config - $page['title'] = 'Configuration'; - - $sg = new SaltGen(); - $config['cookies']['salt'] = $sg->generate(); - $config['secure_trip_salt'] = $sg->generate(); - $config['secure_password_salt'] = $sg->generate(); - - echo Element('page.html', array( - 'body' => Element('installer/config.html', array( - 'config' => $config, - 'more' => $_SESSION['more'], - )), - 'title' => 'Configuration', - 'config' => $config - )); + // Append secure_login_only to $_SESSION['more'] if VICHAN_SECURE_LOGIN_ONLY is set from Docker environment variables + if (getenv('VICHAN_SECURE_LOGIN_ONLY') !== false) { + $secure_login_only = (int)getenv('VICHAN_SECURE_LOGIN_ONLY'); + $_SESSION['more'] .= "\n\$config['cookies']['secure_login_only'] = $secure_login_only;"; + } + + echo Element('page.html', array( + 'body' => Element('installer/config.html', array( + 'config' => $config, + 'more' => $_SESSION['more'], + )), + 'title' => 'Configuration', + 'config' => $config + )); } elseif ($step == 3) { $more = $_POST['more']; unset($_POST['more']); @@ -973,69 +985,85 @@ if ($step == 0) { echo Element('page.html', $page); } } elseif ($step == 4) { - // SQL installation + buildJavascript(); - buildJavascript(); + $sql = @file_get_contents('install.sql') or error("Couldn't load install.sql."); - $sql = @file_get_contents('install.sql') or error("Couldn't load install.sql."); + sql_open(); + $mysql_version = mysql_version(); - sql_open(); - $mysql_version = mysql_version(); + // This code is probably horrible, but what I'm trying + // to do is find all of the SQL queires and put them + // in an array. + preg_match_all("/(^|\n)((SET|CREATE|INSERT).+)\n\n/msU", $sql, $queries); + $queries = $queries[2]; - // This code is probably horrible, but what I'm trying - // to do is find all of the SQL queires and put them - // in an array. - preg_match_all("/(^|\n)((SET|CREATE|INSERT).+)\n\n/msU", $sql, $queries); - $queries = $queries[2]; + $queries[] = Element('posts.sql', array('board' => 'b')); - $queries[] = Element('posts.sql', array('board' => 'b')); + $sql_errors = ''; + $sql_err_count = 0; + foreach ($queries as $query) { + if ($mysql_version < 50503) + $query = preg_replace('/(CHARSET=|CHARACTER SET )utf8mb4/', '$1utf8', $query); + $query = preg_replace('/^([\w\s]*)`([0-9a-zA-Z$_\x{0080}-\x{FFFF}]+)`/u', '$1``$2``', $query); + if (!query($query)) { + $sql_err_count++; + $error = db_error(); + $sql_errors .= "Thank you for installing vichan. Please report any bugs you discover. How do I edit the config files?
'; - $page['title'] = 'Installation complete'; - $page['body'] = 'Thank you for using vichan. Please remember to report any bugs you discover. How do I edit the config files?
'; + // notice and button + $page['body'] .= 'You can now log in to the admin panel at /mod.php using the default credentials: Username: admin, Password: password.
' . + 'Important: For security, please change the administrator password immediately after logging in.
' . + 'SQL errors were encountered when trying to install the database. This may be the result of using a database which is already occupied with a vichan installation; if so, you can probably ignore this.
The errors encountered were:
SQL errors were encountered when trying to install the database. This may be the result of using a database which is already occupied with a vichan installation; if so, you can probably ignore this.
The errors encountered were:
Warning: Ignoring errors is not recommended and may cause installation issues.
' . + 'I couldn\'t remove install.php. You will have to remove it manually.
I couldn\'t remove install.php. You will have to remove it manually.
Thank you for using vichan. Please remember to report any bugs you discover.
'; + $page['title'] = 'Installation complete'; + $page['body'] = 'Thank you for installing vichan. Please report any bugs you discover.
'; - $boards = listBoards(); - foreach ($boards as &$_board) { - setupBoard($_board); - buildIndex(); - } + // onboarding notice and button to mod.php + $page['body'] .= 'You can now log in to the admin panel at /mod.php using the default credentials:
' . + 'Important: For security, please change the administrator password immediately after logging in.
' . + 'I couldn\'t remove install.php. You will have to remove it manually.
I couldn\'t remove install.php. You will have to remove it manually.
| Category | -Test | -Result | -
|---|---|---|
| {{ test.category }} | -{{ test.name }} | -- {% if test.result %} - - {% else %} - {% if test.required %} - {% set errors = errors + 1 %} - - {% else %} - {% set warnings = warnings + 1 %} - - {% endif %} - {% endif %} - | -
There were {{ errors }} error(s) and {{ warnings }} warning(s).
-Click here to ignore errors and attempt installing anyway (not recommended).
- {% else %} -Click here to proceed with installation.
- {% endif %} - {% else %} -There were no errors or warnings. Good!
- - {% endif %} -| Category | +Test | +Result | +
|---|---|---|
| {{ test.category }} | +{{ test.name }} | ++ {% if test.result %} + + {% else %} + {% if test.required %} + {% set errors = errors + 1 %} + + {% else %} + {% set warnings = warnings + 1 %} + + {% endif %} + {% endif %} + | +
There were {{ errors }} error(s) and {{ warnings }} warning(s).
+Warning: Ignoring these problems is not recommended and may cause installation issues.
+ + {% else %} + + {% endif %} + {% else %} +There were no errors or warnings. Good!
+ + {% endif %} + \ No newline at end of file diff --git a/templates/installer/config.html b/templates/installer/config.html index 00a5b241..adf69913 100644 --- a/templates/installer/config.html +++ b/templates/installer/config.html @@ -14,7 +14,7 @@ - +The following is all later configurable. For more options, edit your configuration files after installing.