Compare commits
7 Commits
4255f46fc7
...
dc44d51479
Author | SHA1 | Date | |
---|---|---|---|
dc44d51479 | |||
3c0f2a2ca5 | |||
64e2ff56da | |||
fb0b58dcbf | |||
53ed66dce9 | |||
b53d58df8c | |||
6337fa2dfb |
163
.gitea/workflows/prerequisites.yaml
Normal file
163
.gitea/workflows/prerequisites.yaml
Normal file
@ -0,0 +1,163 @@
|
||||
name: Prerequisites Testing
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-php-version-requirements:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['7.4', '8.1', '8.2', '8.3']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP ${{ matrix.php }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: pdo,pdo_sqlite
|
||||
|
||||
- name: Test PHP version requirement
|
||||
run: |
|
||||
if [[ "${{ matrix.php }}" < "8.2" ]]; then
|
||||
echo "Testing PHP ${{ matrix.php }} - should fail"
|
||||
if php check-prerequisites.php; then
|
||||
echo "ERROR: Should have failed with PHP ${{ matrix.php }}"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Correctly failed with old PHP version"
|
||||
else
|
||||
echo "Testing PHP ${{ matrix.php }} - should pass"
|
||||
php check-prerequisites.php
|
||||
echo "✓ Correctly passed with supported PHP version"
|
||||
fi
|
||||
|
||||
test-extension-progression:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.2', '8.3']
|
||||
container: ['fedora:39', 'debian:bookworm', 'alpine:latest']
|
||||
container: ${{ matrix.container }}
|
||||
|
||||
steps:
|
||||
- name: Install Node.js fnd git or actions
|
||||
run: |
|
||||
if [ -f /etc/fedora-release ]; then
|
||||
dnf install -y nodejs npm git
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
apk add --no-cache nodejs npm git
|
||||
else
|
||||
apt-get update && apt-get install -y nodejs npm git
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install base PHP only
|
||||
run: |
|
||||
if [ -f /etc/fedora-release ]; then
|
||||
dnf --setopt=install_weak_deps=False install -y php php-cli
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
apk add --no-cache php82
|
||||
ln -s /usr/bin/php82 /usr/bin/php
|
||||
else
|
||||
apt-get update && apt-get install -y php
|
||||
fi
|
||||
|
||||
- name: Test failure with missing extensions
|
||||
run: |
|
||||
echo "Testing with base PHP - should fail"
|
||||
if php check-prerequisites.php; then
|
||||
echo "ERROR: Should have failed with missing extensions"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Correctly failed with missing extensions"
|
||||
|
||||
- name: Install PDO extension
|
||||
run: |
|
||||
if [ -f /etc/fedora-release ]; then
|
||||
echo "Not installing PDO on fedora because it includes SQLite support."
|
||||
echo "Will install in subsequent test so this step fails as expected."
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
apk add --no-cache php82-pdo
|
||||
else
|
||||
apt-get install -y php-pdo
|
||||
fi
|
||||
|
||||
- name: Test still fails without SQLite
|
||||
run: |
|
||||
echo "Testing with PDO but no SQLite - should still fail"
|
||||
if php check-prerequisites.php; then
|
||||
echo "ERROR: Should have failed without SQLite"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Correctly failed without SQLite extension"
|
||||
|
||||
- name: Install SQLite extension
|
||||
run: |
|
||||
if [ -f /etc/fedora-release ]; then
|
||||
dnf --setopt=install_weak_deps=False install -y php-pdo
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
apk add --no-cache php82-pdo_sqlite
|
||||
else
|
||||
apt-get install -y php-sqlite3
|
||||
fi
|
||||
|
||||
- name: Test now passes with required extensions
|
||||
run: |
|
||||
echo "Testing with all required extensions - should pass"
|
||||
php check-prerequisites.php
|
||||
echo "✓ All required extensions detected correctly"
|
||||
|
||||
- name: Install recommended extensions and retest
|
||||
run: |
|
||||
if [ -f /etc/fedora-release ]; then
|
||||
dnf install -y php-mbstring php-curl
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
apk add --no-cache php82-mbstring php82-curl
|
||||
else
|
||||
apt-get install -y php-mbstring php-curl
|
||||
fi
|
||||
php check-prerequisites.php
|
||||
echo "✓ Recommended extensions also detected"
|
||||
|
||||
test-permission-scenarios:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: pdo,pdo_sqlite
|
||||
|
||||
- name: Test with unwritable storage directory
|
||||
run: |
|
||||
# Create a non-root user for testing
|
||||
useradd -m -s /bin/bash testuser
|
||||
|
||||
# Make storage unwritable by non-root
|
||||
mkdir -p storage
|
||||
chmod 444 storage
|
||||
chown root:root storage
|
||||
|
||||
# Run as the non-root user - should fail
|
||||
if su testuser -c "php check-prerequisites.php"; then
|
||||
echo "ERROR: Should have failed with unwritable storage"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Correctly failed with unwritable storage"
|
||||
|
||||
# Restore permissions for cleanup
|
||||
chmod 755 storage
|
||||
|
||||
- name: Test with missing directories
|
||||
run: |
|
||||
# Remove required directories
|
||||
rm -rf src templates config
|
||||
|
||||
# Should fail
|
||||
if php check-prerequisites.php; then
|
||||
echo "ERROR: Should have failed with missing directories"
|
||||
exit 1
|
||||
fi
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,4 +15,7 @@ storage/upload/css
|
||||
scratch
|
||||
|
||||
# Build artifacts
|
||||
tkr.tgz
|
||||
tkr.tgz
|
||||
|
||||
# Test logs
|
||||
storage/prerequisite-check.log
|
@ -1,4 +1,5 @@
|
||||
# tkr
|
||||

|
||||

|
||||
|
||||
A lightweight, HTML-only status feed for self-hosted personal websites. Written in PHP. Heavily inspired by [status.cafe](https://status.cafe).
|
||||
|
21
check-prerequisites.php
Normal file
21
check-prerequisites.php
Normal file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* tkr Prerequisites Checker - CLI Diagnostic Tool
|
||||
*
|
||||
* This script provides comprehensive diagnostic information for tkr.
|
||||
* It can be run from the command line or uploaded separately for troubleshooting.
|
||||
*
|
||||
* Usage: php check-prerequisites.php
|
||||
*/
|
||||
|
||||
// Minimal bootstrap just for prerequisites
|
||||
include_once __DIR__ . '/config/bootstrap.php';
|
||||
|
||||
$prerequisites = new Prerequisites();
|
||||
$results = $prerequisites->validate();
|
||||
|
||||
// Exit with appropriate code for shell scripts
|
||||
if (php_sapi_name() === 'cli') {
|
||||
exit(count($prerequisites->getErrors()) > 0 ? 1 : 0);
|
||||
}
|
@ -9,10 +9,9 @@ define('CONFIG_DIR', APP_ROOT . '/config');
|
||||
define('SRC_DIR', APP_ROOT . '/src');
|
||||
define('STORAGE_DIR', APP_ROOT . '/storage');
|
||||
define('TEMPLATES_DIR', APP_ROOT . '/templates');
|
||||
define('TICKS_DIR', STORAGE_DIR . '/ticks');
|
||||
define('DATA_DIR', STORAGE_DIR . '/db');
|
||||
define('CSS_UPLOAD_DIR', STORAGE_DIR . '/upload/css');
|
||||
define('DB_FILE', DATA_DIR . '/tkr.sqlite');
|
||||
define('CSS_UPLOAD_DIR', STORAGE_DIR . '/upload/css');
|
||||
|
||||
// Janky autoloader function
|
||||
// This is a bit more consistent with current frameworks
|
||||
|
@ -14,20 +14,27 @@ if (preg_match('/\.php$/', $path)) {
|
||||
// Define base paths and load classes
|
||||
include_once(dirname(dirname(__FILE__)) . "/config/bootstrap.php");
|
||||
|
||||
// validate that necessary directories exist and are writable
|
||||
$fsMgr = new Filesystem();
|
||||
$fsMgr->validate();
|
||||
// Check prerequisites.
|
||||
$prerequisites = new Prerequisites();
|
||||
$results = $prerequisites->validate();
|
||||
if (count($prerequisites->getErrors()) > 0) {
|
||||
$prerequisites->generateWebSummary($results);
|
||||
exit;
|
||||
}
|
||||
|
||||
// do any necessary database migrations
|
||||
// Do any necessary database migrations
|
||||
$dbMgr = new Database();
|
||||
$dbMgr->migrate();
|
||||
|
||||
// Make sure the initial setup is complete
|
||||
// unless we're already heading to setup
|
||||
//
|
||||
// TODO: Consider simplifying this.
|
||||
// Might not need the custom exception now that the prereq checker is more robust.
|
||||
if (!(preg_match('/setup$/', $path))) {
|
||||
try {
|
||||
// database validation
|
||||
$dbMgr->validate();
|
||||
// Make sure setup has been completed
|
||||
$dbMgr->confirmSetup();
|
||||
} catch (SetupException $e) {
|
||||
$e->handle();
|
||||
exit;
|
||||
|
@ -119,7 +119,7 @@ class Database{
|
||||
}
|
||||
|
||||
// make sure tables that need to be seeded have been
|
||||
private function validateTableContents(): void {
|
||||
public function confirmSetup(): void {
|
||||
$db = self::get();
|
||||
|
||||
// make sure required tables (user, settings) are populated
|
||||
|
@ -13,13 +13,8 @@ class SetupException extends Exception {
|
||||
// but this is a very specific case.
|
||||
public function handle(){
|
||||
switch ($this->setupIssue){
|
||||
case 'storage_missing':
|
||||
case 'storage_permissions':
|
||||
case 'directory_creation':
|
||||
case 'directory_permissions':
|
||||
case 'database_connection':
|
||||
case 'load_classes':
|
||||
case 'table_creation':
|
||||
case 'db_migration':
|
||||
// Unrecoverable errors.
|
||||
// Show error message and exit
|
||||
http_response_code(500);
|
||||
@ -31,7 +26,7 @@ class SetupException extends Exception {
|
||||
// Redirect to setup if we aren't already headed there.
|
||||
// NOTE: Just read directly from init.php instead of
|
||||
// trying to use the config object. This is the initial
|
||||
// setup. It shouldn't assume anything can be loaded.
|
||||
// setup. It shouldn't assume any data can be loaded.
|
||||
$init = require APP_ROOT . '/config/init.php';
|
||||
$currentPath = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
|
||||
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
// Validates that required directories exists
|
||||
// and files have correct formats
|
||||
class Filesystem {
|
||||
public function validate(): void{
|
||||
$this->validateStorageDir();
|
||||
$this->validateStorageSubdirs();
|
||||
}
|
||||
|
||||
// Make sure the storage/ directory exists and is writable
|
||||
private function validateStorageDir(): void{
|
||||
if (!is_dir(STORAGE_DIR)) {
|
||||
throw new SetupException(
|
||||
STORAGE_DIR . "does not exist. Please check your installation.",
|
||||
'storage_missing'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_writable(STORAGE_DIR)) {
|
||||
throw new SetupException(
|
||||
STORAGE_DIR . "is not writable. Exiting.",
|
||||
'storage_permissions'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// validate that the required storage subdirectories exist
|
||||
// attempt to create them if they don't
|
||||
private function validateStorageSubdirs(): void {
|
||||
$storageSubdirs = array();
|
||||
$storageSubdirs[] = CSS_UPLOAD_DIR;
|
||||
$storageSubdirs[] = DATA_DIR;
|
||||
|
||||
foreach($storageSubdirs as $storageSubdir){
|
||||
if (!is_dir($storageSubdir)) {
|
||||
if (!mkdir($storageSubdir, 0770, true)) {
|
||||
throw new SetupException(
|
||||
"Failed to create required directory: $dir",
|
||||
'directory_creation'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable($storageSubdir)) {
|
||||
if (!chmod($storageSubdir, 0770)) {
|
||||
throw new SetupException(
|
||||
"Required directory is not writable: $dir",
|
||||
'directory_permissions'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
564
src/Framework/Prerequisites/Prerequisites.php
Normal file
564
src/Framework/Prerequisites/Prerequisites.php
Normal file
@ -0,0 +1,564 @@
|
||||
<?php
|
||||
/**
|
||||
* tkr Prerequisites Checker
|
||||
*
|
||||
* This class checks all system requirements for tkr and provides
|
||||
* detailed logging of any missing components or configuration issues.
|
||||
*
|
||||
* ZERO DEPENDENCIES - Uses only core PHP functions available since PHP 5.3
|
||||
*/
|
||||
|
||||
class Prerequisites {
|
||||
private $checks = array();
|
||||
private $warnings = array();
|
||||
private $errors = array();
|
||||
private $baseDir;
|
||||
private $logFile;
|
||||
private $isCli;
|
||||
private $isWeb;
|
||||
|
||||
public function __construct() {
|
||||
$this->isCli = php_sapi_name() === 'cli';
|
||||
$this->isWeb = !$this->isCli && isset($_SERVER['HTTP_HOST']);
|
||||
$this->baseDir = APP_ROOT;
|
||||
$this->logFile = $this->baseDir . '/storage/prerequisite-check.log';
|
||||
|
||||
if ($this->isWeb) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
/** Log validation output
|
||||
*
|
||||
* This introduces a chicken-and-egg problem, because
|
||||
* if the storage directory isn't writable, this will fail.
|
||||
* In that case, I'll just write to stdout.
|
||||
*
|
||||
*/
|
||||
private function log($message, $overwrite=false) {
|
||||
$logDir = dirname($this->logFile);
|
||||
//print("Log dir: {$logDir}");
|
||||
if (!is_dir($logDir)) {
|
||||
if (!@mkdir($logDir, 0770, true)) {
|
||||
// Can't create storage dir - just output, don't log to file
|
||||
if ($this->isCli) {
|
||||
echo $message . "\n";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$flags = LOCK_EX;
|
||||
if (!$overwrite) {
|
||||
$flags |= FILE_APPEND;
|
||||
}
|
||||
|
||||
// Try to write to log file
|
||||
if (@file_put_contents($this->logFile, $message . "\n", $flags) === false) {
|
||||
// Logging failed, but continue - just output to CLI if possible
|
||||
if ($this->isCli) {
|
||||
echo "Warning: Could not write to log file\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isCli) {
|
||||
echo $message . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function addCheck($name, $status, $message, $severity = 'info') {
|
||||
$this->checks[] = array(
|
||||
'name' => $name,
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'severity' => $severity
|
||||
);
|
||||
|
||||
if ($severity === 'error') {
|
||||
$this->errors[] = $message;
|
||||
} elseif ($severity === 'warning') {
|
||||
$this->warnings[] = $message;
|
||||
}
|
||||
|
||||
$statusIcon = $status ? '✓' : '✗';
|
||||
$this->log("[{$statusIcon}] {$name}: {$message}");
|
||||
}
|
||||
|
||||
private function checkPhpVersion() {
|
||||
$minVersion = '8.2.0';
|
||||
$currentVersion = PHP_VERSION;
|
||||
$versionOk = version_compare($currentVersion, $minVersion, '>=');
|
||||
|
||||
if ($versionOk) {
|
||||
$this->addCheck(
|
||||
'PHP Version',
|
||||
true,
|
||||
"PHP {$currentVersion} (meets minimum requirement of {$minVersion})"
|
||||
);
|
||||
} else {
|
||||
$this->addCheck(
|
||||
'PHP Version',
|
||||
false,
|
||||
"PHP {$currentVersion} is below minimum requirement of {$minVersion}",
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
return $versionOk;
|
||||
}
|
||||
|
||||
private function checkRequiredExtensions() {
|
||||
$requiredExtensions = array('PDO', 'pdo_sqlite');
|
||||
|
||||
$allRequired = true;
|
||||
foreach ($requiredExtensions as $ext) {
|
||||
$loaded = extension_loaded($ext);
|
||||
$this->addCheck(
|
||||
"PHP Extension: {$ext}",
|
||||
$loaded,
|
||||
$loaded ? 'Available' : "Missing (REQUIRED) - {$ext}",
|
||||
$loaded ? 'info' : 'error'
|
||||
);
|
||||
if (!$loaded) {
|
||||
$allRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $allRequired;
|
||||
}
|
||||
|
||||
private function checkRecommendedExtensions() {
|
||||
$recommendedExtensions = array('mbstring', 'fileinfo', 'session');
|
||||
|
||||
foreach ($recommendedExtensions as $ext) {
|
||||
$loaded = extension_loaded($ext);
|
||||
$this->addCheck(
|
||||
"PHP Extension: {$ext}",
|
||||
$loaded,
|
||||
$loaded ? 'Available' : "Missing (recommended) - {$ext}",
|
||||
$loaded ? 'info' : 'warning'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkDirectoryStructure() {
|
||||
$baseDir = $this->baseDir;
|
||||
$requiredDirs = array(
|
||||
'config' => 'Configuration files',
|
||||
'public' => 'Web server document root',
|
||||
'src' => 'Application source code',
|
||||
'storage' => 'Data storage (must be writable)',
|
||||
'templates' => 'Template files'
|
||||
);
|
||||
|
||||
$allPresent = true;
|
||||
foreach ($requiredDirs as $dir => $description) {
|
||||
$path = $baseDir . '/' . $dir;
|
||||
$exists = is_dir($path);
|
||||
$this->addCheck(
|
||||
"Directory: {$dir}",
|
||||
$exists,
|
||||
$exists ? "Present - {$description}" : "Missing - {$description} at {$path}",
|
||||
$exists ? 'info' : 'error'
|
||||
);
|
||||
if (!$exists) {
|
||||
$allPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $allPresent;
|
||||
}
|
||||
|
||||
private function checkStoragePermissions() {
|
||||
// Issue a warning if running as root in CLI context
|
||||
// Write out guidance for storage directory permissions
|
||||
// if running the CLI script as root (since it will always appear to be writable)
|
||||
if ($this->isCli && function_exists('posix_getuid') && posix_getuid() === 0) {
|
||||
$this->addCheck(
|
||||
'Root User Warning',
|
||||
false,
|
||||
'Running as root - permission checks may be inaccurate. After setup, ensure storage/ is owned by your web server user',
|
||||
'warning'
|
||||
);
|
||||
} elseif ($this->isCli && !function_exists('posix_getuid')) {
|
||||
$this->addCheck(
|
||||
'POSIX Extension',
|
||||
false,
|
||||
'POSIX extension not available - cannot detect if running as root',
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
|
||||
$storageDirs = array(
|
||||
'storage',
|
||||
'storage/db',
|
||||
'storage/upload',
|
||||
'storage/upload/css'
|
||||
);
|
||||
|
||||
$allWritable = true;
|
||||
foreach ($storageDirs as $dir) {
|
||||
$path = $this->baseDir . '/' . $dir;
|
||||
|
||||
if (!is_dir($path)) {
|
||||
// Try to create the directory
|
||||
$created = @mkdir($path, 0770, true);
|
||||
if ($created) {
|
||||
$this->addCheck(
|
||||
"Storage Directory: {$dir}",
|
||||
true,
|
||||
"Created with correct permissions (0770)"
|
||||
);
|
||||
} else {
|
||||
$this->addCheck(
|
||||
"Storage Directory: {$dir}",
|
||||
false,
|
||||
"Could not create directory: {$dir}",
|
||||
'error'
|
||||
);
|
||||
$allWritable = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$writable = is_writable($path);
|
||||
$permissions = substr(sprintf('%o', fileperms($path)), -4);
|
||||
|
||||
$this->addCheck(
|
||||
"Storage Permissions: {$dir}",
|
||||
$writable,
|
||||
$writable ? "Writable (permissions: {$permissions})" : "Not writable (permissions: {$permissions})",
|
||||
$writable ? 'info' : 'error'
|
||||
);
|
||||
|
||||
if (!$writable) {
|
||||
$allWritable = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $allWritable;
|
||||
}
|
||||
|
||||
private function checkWebServerConfig() {
|
||||
if ($this->isCli) {
|
||||
$this->addCheck(
|
||||
'Web Server Test',
|
||||
false,
|
||||
'Cannot test web server configuration from CLI - run via web browser',
|
||||
'warning'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we're being served from the correct document root
|
||||
$documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
|
||||
$expectedPath = realpath($this->baseDir . '/public');
|
||||
$correctRoot = ($documentRoot === $expectedPath);
|
||||
|
||||
$this->addCheck(
|
||||
'Document Root',
|
||||
$correctRoot,
|
||||
$correctRoot ?
|
||||
"Correctly set to {$expectedPath}" :
|
||||
"Should be {$expectedPath}, currently {$documentRoot}",
|
||||
$correctRoot ? 'info' : 'warning'
|
||||
);
|
||||
|
||||
// Check for URL rewriting
|
||||
$rewriteWorking = isset($_SERVER['REQUEST_URI']);
|
||||
$this->addCheck(
|
||||
'URL Rewriting',
|
||||
$rewriteWorking,
|
||||
$rewriteWorking ? 'Available' : 'May not be properly configured',
|
||||
$rewriteWorking ? 'info' : 'warning'
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function checkConfiguration() {
|
||||
$configFile = $this->baseDir . '/config/init.php';
|
||||
$configExists = file_exists($configFile);
|
||||
|
||||
if (!$configExists) {
|
||||
$this->addCheck(
|
||||
'Configuration File',
|
||||
false,
|
||||
'config/init.php not found',
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = include $configFile;
|
||||
$hasBaseUrl = isset($config['base_url']) && !empty($config['base_url']);
|
||||
$hasBasePath = isset($config['base_path']) && !empty($config['base_path']);
|
||||
|
||||
$this->addCheck(
|
||||
'Configuration File',
|
||||
true,
|
||||
'config/init.php exists and is readable'
|
||||
);
|
||||
|
||||
$this->addCheck(
|
||||
'Base URL Configuration',
|
||||
$hasBaseUrl,
|
||||
$hasBaseUrl ? "Set to: {$config['base_url']}" : 'Not configured',
|
||||
$hasBaseUrl ? 'info' : 'warning'
|
||||
);
|
||||
|
||||
$this->addCheck(
|
||||
'Base Path Configuration',
|
||||
$hasBasePath,
|
||||
$hasBasePath ? "Set to: {$config['base_path']}" : 'Not configured',
|
||||
$hasBasePath ? 'info' : 'warning'
|
||||
);
|
||||
|
||||
return $hasBaseUrl && $hasBasePath;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->addCheck(
|
||||
'Configuration File',
|
||||
false,
|
||||
'Error reading config/init.php: ' . $e->getMessage(),
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function checkDatabase() {
|
||||
$dbFile = $this->baseDir . '/storage/db/tkr.sqlite';
|
||||
$dbDir = dirname($dbFile);
|
||||
|
||||
if (!is_dir($dbDir)) {
|
||||
$created = @mkdir($dbDir, 0770, true);
|
||||
if (!$created) {
|
||||
$this->addCheck(
|
||||
'Database Directory',
|
||||
false,
|
||||
'Could not create storage/db directory',
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$canCreateDb = is_writable($dbDir);
|
||||
$this->addCheck(
|
||||
'Database Directory',
|
||||
$canCreateDb,
|
||||
$canCreateDb ? 'Writable - can create database' : 'Not writable - cannot create database',
|
||||
$canCreateDb ? 'info' : 'error'
|
||||
);
|
||||
|
||||
if (file_exists($dbFile)) {
|
||||
$dbReadable = is_readable($dbFile);
|
||||
$dbWritable = is_writable($dbFile);
|
||||
|
||||
$this->addCheck(
|
||||
'Database File',
|
||||
$dbReadable && $dbWritable,
|
||||
$dbReadable && $dbWritable ? 'Exists and is accessible' : 'Exists but has permission issues',
|
||||
$dbReadable && $dbWritable ? 'info' : 'error'
|
||||
);
|
||||
} else {
|
||||
$this->addCheck(
|
||||
'Database File',
|
||||
true,
|
||||
'Will be created on first run'
|
||||
);
|
||||
}
|
||||
|
||||
return $canCreateDb;
|
||||
}
|
||||
|
||||
// validate prereqs
|
||||
// runs on each request and can be run from CLI
|
||||
public function validate() {
|
||||
$this->log("=== tkr prerequisites check started at " . date('Y-m-d H:i:s') . " ===", true);
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->log("\n🔍 Validating prerequisites...\n");
|
||||
}
|
||||
|
||||
$results = array(
|
||||
'php_version' => $this->checkPhpVersion(),
|
||||
'critical_extensions' => $this->checkRequiredExtensions(),
|
||||
'directory_structure' => $this->checkDirectoryStructure(),
|
||||
'storage_permissions' => $this->checkStoragePermissions(),
|
||||
'web_server' => $this->checkWebServerConfig(),
|
||||
'configuration' => $this->checkConfiguration(),
|
||||
'database' => $this->checkDatabase()
|
||||
);
|
||||
|
||||
// Check recommended extensions too
|
||||
$this->checkRecommendedExtensions();
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->generateCliSummary($results);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display web-friendly error page when minimum requirements aren't met
|
||||
*/
|
||||
public function generateWebSummary() {
|
||||
echo '<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>tkr - Setup Required</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 2rem; line-height: 1.6; background: #f8f9fa; }
|
||||
.container { max-width: 800px; margin: 0 auto; background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.header { text-align: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 2px solid #dee2e6; }
|
||||
.header h1 { color: #dc3545; margin: 0; }
|
||||
.header p { color: #6c757d; margin: 0.5rem 0 0 0; }
|
||||
.error-item { margin: 1rem 0; padding: 1rem; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; border-left: 4px solid #dc3545; }
|
||||
.warning-item { margin: 1rem 0; padding: 1rem; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; border-left: 4px solid #ffc107; }
|
||||
.error-title { font-weight: 600; color: #721c24; margin-bottom: 0.5rem; }
|
||||
.warning-title { font-weight: 600; color: #856404; margin-bottom: 0.5rem; }
|
||||
.resolution { margin-top: 2rem; padding: 1rem; background: #e9ecef; border-radius: 4px; }
|
||||
.resolution h3 { margin-top: 0; color: #495057; }
|
||||
.resolution ul { margin: 0; }
|
||||
.resolution li { margin: 0.5rem 0; }
|
||||
.log-info { margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; font-size: 0.9em; color: #6c757d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>⚠️ Setup Required</h1>
|
||||
<p>tkr cannot start due to system configuration issues</p>
|
||||
</div>';
|
||||
|
||||
$hasErrors = false;
|
||||
$hasWarnings = false;
|
||||
|
||||
// Display errors
|
||||
foreach ($this->checks as $check) {
|
||||
if (!$check['status'] && $check['severity'] === 'error') {
|
||||
if (!$hasErrors) {
|
||||
echo '<h2>Critical Issues</h2>';
|
||||
$hasErrors = true;
|
||||
}
|
||||
echo '<div class="error-item">
|
||||
<div class="error-title">✗ ' . htmlspecialchars($check['name']) . '</div>
|
||||
' . htmlspecialchars($check['message']) . '
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Display warnings
|
||||
foreach ($this->checks as $check) {
|
||||
if (!$check['status'] && $check['severity'] === 'warning') {
|
||||
if (!$hasWarnings) {
|
||||
echo '<h2>Warnings</h2>';
|
||||
$hasWarnings = true;
|
||||
}
|
||||
echo '<div class="warning-item">
|
||||
<div class="warning-title">⚠ ' . htmlspecialchars($check['name']) . '</div>
|
||||
' . htmlspecialchars($check['message']) . '
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution steps
|
||||
echo '<div class="resolution">
|
||||
<h3>How to Fix These Issues</h3>
|
||||
<ul>';
|
||||
|
||||
if (!version_compare(PHP_VERSION, '8.2.0', '>=')) {
|
||||
echo '<li><strong>PHP Version:</strong> Contact your hosting provider to upgrade PHP to version 8.2 or higher</li>';
|
||||
}
|
||||
|
||||
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
|
||||
echo '<li><strong>SQLite Support:</strong> Contact your hosting provider to enable PDO and PDO_SQLITE extensions</li>';
|
||||
}
|
||||
|
||||
if (count($this->errors) > 0) {
|
||||
echo '<li><strong>File Permissions:</strong> Ensure the storage directory and subdirectories are writable by the web server</li>';
|
||||
echo '<li><strong>Missing Directories:</strong> Upload the complete tkr application with all required directories</li>';
|
||||
}
|
||||
|
||||
echo ' </ul>
|
||||
<p><strong>Need Help?</strong> Check the tkr documentation or contact your hosting provider with the error details above.</p>
|
||||
</div>
|
||||
|
||||
<div class="log-info">
|
||||
<p><strong>Technical Details:</strong> Full diagnostic information has been logged to ' . htmlspecialchars($this->logFile) . '</p>
|
||||
<p><strong>Check Time:</strong> ' . date('Y-m-d H:i:s') . '</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>';
|
||||
}
|
||||
|
||||
private function generateCliSummary($results) {
|
||||
$this->log("\n" . str_repeat("=", 60));
|
||||
$this->log("PREREQUISITE CHECK SUMMARY");
|
||||
$this->log(str_repeat("=", 60));
|
||||
|
||||
$totalChecks = count($this->checks);
|
||||
$passedChecks = 0;
|
||||
foreach ($this->checks as $check) {
|
||||
if ($check['status']) {
|
||||
$passedChecks++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log("Total checks: {$totalChecks}");
|
||||
$this->log("Passed: {$passedChecks}");
|
||||
$this->log("Errors: " . count($this->errors));
|
||||
$this->log("Warnings: " . count($this->warnings));
|
||||
|
||||
if (count($this->errors) === 0) {
|
||||
$this->log("\n✅ ALL PREREQUISITES SATISFIED");
|
||||
$this->log("tkr should install and run successfully.");
|
||||
} else {
|
||||
$this->log("\n❌ CRITICAL ISSUES FOUND");
|
||||
$this->log("The following issues must be resolved before installing tkr:");
|
||||
foreach ($this->errors as $error) {
|
||||
$this->log(" • {$error}");
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->warnings) > 0) {
|
||||
$this->log("\n⚠️ WARNINGS:");
|
||||
foreach ($this->warnings as $warning) {
|
||||
$this->log(" • {$warning}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isCli && function_exists('posix_getuid') && posix_getuid() === 0) {
|
||||
$this->log("\n📋 ROOT USER SETUP RECOMMENDATIONS:");
|
||||
$this->log("After uploading to your web server,");
|
||||
$this->log("make sure the storage directory is writable by the web server user by running:");
|
||||
$this->log(" chown -R www-data:www-data storage/ # Debian/Ubuntu");
|
||||
$this->log(" chown -R apache:apache storage/ # RHEL/CentOS/Fedora");
|
||||
$this->log(" chmod -R 770 storage/ # Ensure writability");
|
||||
}
|
||||
|
||||
$this->log("\n📝 Full log saved to: " . $this->logFile);
|
||||
$this->log("=== Check completed at " . date('Y-m-d H:i:s') . " ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of errors for external use
|
||||
*/
|
||||
public function getErrors() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of warnings for external use
|
||||
*/
|
||||
public function getWarnings() {
|
||||
return $this->warnings;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user