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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 88 additions & 97 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,114 +1,105 @@
# =============================================================================
# CodeIgniter 4 Chat Application - Environment Configuration
# =============================================================================
# Copy this file to .env and update the values for your environment.
# IMPORTANT: Never commit .env to version control!
#
# This file is used by both Docker Compose and CodeIgniter.
# =============================================================================
# Generated by `php spark env:dump`. Do not edit by hand.
# Copy this file to .env and replace every CHANGE-ME value.

# -----------------------------------------------------------------------------
# Application Settings
# -----------------------------------------------------------------------------
# Environment: development, testing, or production
# Application
# Active CodeIgniter runtime environment.
# Type: enum (development, testing, production); required
CI_ENVIRONMENT=development

# Application URL (used by CodeIgniter for generating links)
# For Docker: Use the host port, not the container port
# Public application URL, including its scheme.
# Type: url; required
APP_URL=http://localhost:8000

# Port to expose the web application (default: 8000)
# Host port exposed by the web container.
# Type: int (1–65535); optional
APP_PORT=8000

# -----------------------------------------------------------------------------
# Database Configuration
# -----------------------------------------------------------------------------
# This application supports both MySQL and SQLite databases.
#
# QUICK START WITH SQLITE (recommended for beginners):
# 1. Set DB_DRIVER=SQLite3
# 2. Run: php spark migrate
# 3. That's it! No database server needed.
#
# FOR MYSQL (production or Docker):
# 1. Set DB_DRIVER=MySQLi (or leave unset, it's the default)
# 2. Configure the MySQL settings below
# 3. Run: php spark migrate
# -----------------------------------------------------------------------------

# Database driver: MySQLi (default) or SQLite3
# Set to SQLite3 for easy local development without a database server
# Database
# Database driver used by the application.
# Type: enum (MySQLi, SQLite3); required
DB_DRIVER=MySQLi

# -----------------------------------------------------------------------------
# MySQL Configuration (only needed if DB_DRIVER=MySQLi)
# -----------------------------------------------------------------------------
# These settings are used by both Docker Compose and CodeIgniter.

# Database name
# MySQL database name.
# Type: string; required when DB_DRIVER=MySQLi
DB_DATABASE=ci4_chat

# Database user (application account)
# MySQL application account.
# Type: string; required when DB_DRIVER=MySQLi
DB_USERNAME=ci4_user
DB_PASSWORD=ci4_password

# Database root password (only used by MySQL container)
DB_ROOT_PASSWORD=rootpassword

# Port to expose MySQL on the host (default: 3307 to avoid conflicts)
# Connect with a database client using localhost:3307
# Password for the MySQL application account.
# Type: string; required when DB_DRIVER=MySQLi
DB_PASSWORD=CHANGE-ME
# MySQL root password used only by Docker Compose.
# Type: string; optional
DB_ROOT_PASSWORD=CHANGE-ME
# Host port exposed by the MySQL container.
# Type: int (1–65535); optional
DB_PORT=3307

# -----------------------------------------------------------------------------
# SQLite Configuration (only needed if DB_DRIVER=SQLite3)
# -----------------------------------------------------------------------------
# When using SQLite, the database file is automatically created at:
# writable/database/chat.db
#
# No additional configuration is required for SQLite.
# The database directory is created automatically when the app starts.

# -----------------------------------------------------------------------------
# WebSocket Configuration
# -----------------------------------------------------------------------------
# Port for the WebSocket server
# WebSocket
# Host port exposed by the WebSocket server.
# Type: int (1–65535); optional
WEBSOCKET_PORT=8080

# WebSocket URL for the frontend to connect to
# This should be accessible from the user's browser
# Browser-accessible WebSocket URL using ws or wss.
# Type: websocket_url; required
WEBSOCKET_URL=ws://localhost:8080

# -----------------------------------------------------------------------------
# CodeIgniter Settings (used when running locally without Docker)
# -----------------------------------------------------------------------------
# Uncomment and modify these for local development without Docker.
# When using Docker, the docker-compose.yml overrides these.

# database.default.hostname=localhost
# database.default.database=ci4_chat
# database.default.username=ci4_user
# database.default.password=ci4_password
# database.default.DBDriver=MySQLi
# database.default.port=3306

# -----------------------------------------------------------------------------
# Optional: Security Settings
# -----------------------------------------------------------------------------
# Encryption key for CodeIgniter (generate with: php spark key:generate)
# Secret reserved for signing WebSocket authentication material.
# Type: string; required
WEBSOCKET_TOKEN_SECRET=CHANGE-ME

# Development
# Allows destructive benchmark data seeding outside production.
# Type: bool; optional
BENCHMARK_MODE=false

# Push notifications
# Public VAPID key exposed to browser push clients.
# Type: string; optional
# push.vapidPublicKey=

# CodeIgniter overrides
# Direct override for the default database host.
# Type: string; optional
# database.default.hostname=
# Direct override for the default database name.
# Type: string; optional
# database.default.database=
# Direct override for the default database user.
# Type: string; optional
# database.default.username=
# Direct override for the default database password.
# Type: string; optional
# database.default.password=
# Direct override for the default database driver.
# Type: enum (MySQLi, SQLite3); optional
# database.default.DBDriver=
# Direct override for the default database port.
# Type: int (1–65535); optional
# database.default.port=
# CodeIgniter encryption key.
# Type: string; optional
# encryption.key=

# -----------------------------------------------------------------------------
# Optional: Email Settings
# -----------------------------------------------------------------------------
# email.protocol=smtp
# Email
# Email delivery protocol.
# Type: enum (mail, sendmail, smtp); optional
# email.protocol=
# SMTP server hostname.
# Type: string; optional
# email.SMTPHost=
# SMTP account username.
# Type: string; optional
# email.SMTPUser=
# SMTP account password.
# Type: string; optional
# email.SMTPPass=
# email.SMTPPort=587
# email.SMTPCrypto=tls

# -----------------------------------------------------------------------------
# Optional: Session Settings
# -----------------------------------------------------------------------------
# session.driver=CodeIgniter\Session\Handlers\FileHandler
# session.savePath=writable/session
# SMTP server port.
# Type: int (1–65535); optional
# email.SMTPPort=
# SMTP transport encryption mode.
# Type: enum (tls, ssl); optional
# email.SMTPCrypto=

# Session
# Fully-qualified CodeIgniter session handler class.
# Type: string; optional
# session.driver=
# Session handler storage location.
# Type: string; optional
# session.savePath=
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ jobs:
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: Validate environment configuration
env:
DB_PASSWORD: db-${{ github.run_id }}-${{ github.run_attempt }}-validation-value
DB_ROOT_PASSWORD: root-${{ github.run_id }}-${{ github.run_attempt }}-validation-value
WEBSOCKET_TOKEN_SECRET: websocket-${{ github.sha }}-${{ github.run_id }}
run: |
cp .env.example .env
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=${DB_PASSWORD}|" .env
sed -i "s|^DB_ROOT_PASSWORD=.*|DB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}|" .env
sed -i "s|^WEBSOCKET_TOKEN_SECRET=.*|WEBSOCKET_TOKEN_SECRET=${WEBSOCKET_TOKEN_SECRET}|" .env
php spark env:check --no-header
php spark env:diff --no-header

- name: Check PHP coding style
run: composer cs-check

Expand Down
32 changes: 32 additions & 0 deletions app/Commands/EnvCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Commands;

use App\Services\EnvValidator;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\EnvSchema;

final class EnvCheck extends BaseCommand
{
protected $group = 'Environment';
protected $name = 'env:check';
protected $description = 'Validates the current environment configuration.';

public function run(array $params): int
{
$errors = new EnvValidator(new EnvSchema()->schema)->validate();
if ($errors === []) {
CLI::write('Environment configuration is valid.', 'green');

return EXIT_SUCCESS;
}

CLI::error('Environment configuration is invalid:');
foreach ($errors as $error) {
CLI::error('- ' . $error);
}

return EXIT_ERROR;
}
}
45 changes: 45 additions & 0 deletions app/Commands/EnvDiff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Commands;

use App\Services\EnvFileManager;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\EnvSchema;

final class EnvDiff extends BaseCommand
{
protected $group = 'Environment';
protected $name = 'env:diff';
protected $description = 'Checks .env.example for drift from the environment schema.';

public function run(array $params): int
{
$path = ROOTPATH . '.env.example';
$contents = is_file($path) ? file_get_contents($path) : false;
if ($contents === false) {
CLI::error('Unable to read ' . $path);

return EXIT_ERROR;
}

$diff = new EnvFileManager(new EnvSchema()->schema)->diff($contents);
if (! $diff['stale']) {
CLI::write('.env.example matches the environment schema.', 'green');

return EXIT_SUCCESS;
}

foreach ($diff['missing'] as $name) {
CLI::error('Missing from .env.example: ' . $name);
}
foreach ($diff['unexpected'] as $name) {
CLI::error('Not declared in EnvSchema: ' . $name);
}
if ($diff['missing'] === [] && $diff['unexpected'] === []) {
CLI::error('.env.example content is stale. Run php spark env:dump.');
}

return EXIT_ERROR;
}
}
30 changes: 30 additions & 0 deletions app/Commands/EnvDocs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Commands;

use App\Services\EnvFileManager;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\EnvSchema;

final class EnvDocs extends BaseCommand
{
protected $group = 'Environment';
protected $name = 'env:docs';
protected $description = 'Regenerates environment configuration documentation.';

public function run(array $params): int
{
$path = ROOTPATH . 'docs/configuration.md';
$contents = new EnvFileManager(new EnvSchema()->schema)->renderDocumentation();
if (file_put_contents($path, $contents) === false) {
CLI::error('Unable to write ' . $path);

return EXIT_ERROR;
}

CLI::write('Generated ' . $path, 'green');

return EXIT_SUCCESS;
}
}
30 changes: 30 additions & 0 deletions app/Commands/EnvDump.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Commands;

use App\Services\EnvFileManager;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\EnvSchema;

final class EnvDump extends BaseCommand
{
protected $group = 'Environment';
protected $name = 'env:dump';
protected $description = 'Regenerates .env.example from the environment schema.';

public function run(array $params): int
{
$path = ROOTPATH . '.env.example';
$contents = new EnvFileManager(new EnvSchema()->schema)->renderExample();
if (file_put_contents($path, $contents) === false) {
CLI::error('Unable to write ' . $path);

return EXIT_ERROR;
}

CLI::write('Generated ' . $path, 'green');

return EXIT_SUCCESS;
}
}
15 changes: 15 additions & 0 deletions app/Config/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,19 @@ class App extends BaseConfig
* @see http://www.w3.org/TR/CSP/
*/
public bool $CSPEnabled = true;

public function __construct()
{
parent::__construct();

if (ENVIRONMENT === 'testing') {
return;
}

$appUrl = env('APP_URL', '');
$scheme = is_string($appUrl) ? parse_url($appUrl, PHP_URL_SCHEME) : null;
if (is_string($appUrl) && filter_var($appUrl, FILTER_VALIDATE_URL) !== false && in_array($scheme, ['http', 'https'], true)) {
$this->baseURL = rtrim($appUrl, '/') . '/';
}
}
}
Loading
Loading