Make first time setup more robust. (#58)
Split out prerequisite validation from creation. Distinguish system prereqs from application prereqs. Support URL autodetection to eliminate requirement to edit file as part of setup. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/58 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
parent
e5945e91a3
commit
086c3e2ebb
@ -20,14 +20,14 @@ jobs:
|
||||
run: |
|
||||
if [[ "${{ matrix.php }}" < "8.2" ]]; then
|
||||
echo "Testing PHP ${{ matrix.php }} - should fail"
|
||||
if php check-prerequisites.php; then
|
||||
if php tkr-setup.php --validate-only; 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
|
||||
php tkr-setup.php --validate-only
|
||||
echo "✓ Correctly passed with supported PHP version"
|
||||
fi
|
||||
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Test failure with missing extensions
|
||||
run: |
|
||||
echo "Testing with base PHP - should fail"
|
||||
if php check-prerequisites.php; then
|
||||
if php tkr-setup.php --validate-only; then
|
||||
echo "ERROR: Should have failed with missing extensions"
|
||||
exit 1
|
||||
fi
|
||||
@ -86,7 +86,7 @@ jobs:
|
||||
- name: Test still fails without SQLite
|
||||
run: |
|
||||
echo "Testing with PDO but no SQLite - should still fail"
|
||||
if php check-prerequisites.php; then
|
||||
if php tkr-setup.php --validate-only; then
|
||||
echo "ERROR: Should have failed without SQLite"
|
||||
exit 1
|
||||
fi
|
||||
@ -105,7 +105,7 @@ jobs:
|
||||
- name: Test now passes with required extensions
|
||||
run: |
|
||||
echo "Testing with all required extensions - should pass"
|
||||
php check-prerequisites.php
|
||||
php tkr-setup.php --validate-only
|
||||
echo "✓ All required extensions detected correctly"
|
||||
|
||||
- name: Install recommended extensions and retest
|
||||
@ -117,7 +117,7 @@ jobs:
|
||||
else
|
||||
apt-get install -y php-mbstring php-curl
|
||||
fi
|
||||
php check-prerequisites.php
|
||||
php tkr-setup.php --validate-only
|
||||
echo "✓ Recommended extensions also detected"
|
||||
|
||||
test-permission-scenarios:
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
chown root:root storage
|
||||
|
||||
# Run as the non-root user - should fail
|
||||
if su testuser -c "php check-prerequisites.php"; then
|
||||
if su testuser -c "php tkr-setup.php --validate-only"; then
|
||||
echo "ERROR: Should have failed with unwritable storage"
|
||||
exit 1
|
||||
fi
|
||||
@ -157,7 +157,7 @@ jobs:
|
||||
rm -rf src templates
|
||||
|
||||
# Should fail
|
||||
if php check-prerequisites.php; then
|
||||
if php tkr-setup.php --validate-only; then
|
||||
echo "ERROR: Should have failed with missing directories"
|
||||
exit 1
|
||||
fi
|
@ -1,21 +0,0 @@
|
||||
#!/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);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
// initial configuration. These need to be set on first run so the app loads properly.
|
||||
// Other settings can be defined in the admin page that loads on first run.
|
||||
return [
|
||||
'base_url' => 'http://localhost',
|
||||
'base_path' => '/tkr/',
|
||||
];
|
@ -22,27 +22,37 @@ include_once(dirname(dirname(__FILE__)) . "/config/bootstrap.php");
|
||||
* Validate application state before processing request
|
||||
*/
|
||||
|
||||
// Check prerequisites (includes database connection and migrations)
|
||||
// Check system requirements first
|
||||
$prerequisites = new Prerequisites();
|
||||
if (!$prerequisites->validate()) {
|
||||
if (!$prerequisites->validateSystem()) {
|
||||
$prerequisites->generateWebSummary();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check application state and create missing components if needed
|
||||
if (!$prerequisites->validateApplication()) {
|
||||
if (!$prerequisites->createMissing()) {
|
||||
$prerequisites->generateWebSummary();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the working database connection from prerequisites
|
||||
$db = $prerequisites->getDatabase();
|
||||
|
||||
// Make sure the initial setup is complete unless we're already heading to setup
|
||||
if (!(preg_match('/setup$/', $path))) {
|
||||
// Check if setup is complete (user exists and URL is configured)
|
||||
if (!(preg_match('/tkr-setup$/', $path))) {
|
||||
try {
|
||||
// Make sure required tables (user, settings) are populated
|
||||
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
||||
$settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
||||
$config = (new ConfigModel($db))->get();
|
||||
|
||||
// If either required table has no records, redirect to setup.
|
||||
if ($user_count === 0 || $settings_count === 0){
|
||||
$init = require APP_ROOT . '/config/init.php';
|
||||
header('Location: ' . $init['base_path'] . 'setup');
|
||||
$hasUser = $user_count > 0;
|
||||
$hasUrl = !empty($config->baseUrl) && !empty($config->basePath);
|
||||
|
||||
if (!$hasUser || !$hasUrl) {
|
||||
// Redirect to setup with auto-detected URL
|
||||
$autodetected = Util::getAutodetectedUrl();
|
||||
header('Location: ' . $autodetected['fullUrl'] . '/tkr-setup');
|
||||
exit;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
|
@ -9,6 +9,21 @@ class AdminController extends Controller {
|
||||
|
||||
public function showSetup(){
|
||||
$data = $this->getAdminData(true);
|
||||
|
||||
// Auto-detect URL and pre-fill if not already configured
|
||||
if (empty($data['config']->baseUrl) || empty($data['config']->basePath)) {
|
||||
$autodetected = Util::getAutodetectedUrl();
|
||||
$data['autodetectedUrl'] = $autodetected;
|
||||
|
||||
// Pre-fill empty values with auto-detected ones
|
||||
if (empty($data['config']->baseUrl)) {
|
||||
$data['config']->baseUrl = $autodetected['baseUrl'];
|
||||
}
|
||||
if (empty($data['config']->basePath)) {
|
||||
$data['config']->basePath = $autodetected['basePath'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->render("admin.php", $data);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
* 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 {
|
||||
@ -17,6 +16,12 @@ class Prerequisites {
|
||||
private $isCli;
|
||||
private $isWeb;
|
||||
private $database = null;
|
||||
private $storageSubdirs = [
|
||||
'storage/db',
|
||||
'storage/logs',
|
||||
'storage/upload',
|
||||
'storage/upload/css',
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
$this->isCli = php_sapi_name() === 'cli';
|
||||
@ -175,7 +180,7 @@ class Prerequisites {
|
||||
return $allPresent;
|
||||
}
|
||||
|
||||
private function checkStoragePermissions() {
|
||||
private function checkExistingStoragePermissions() {
|
||||
// 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)
|
||||
@ -195,39 +200,17 @@ class Prerequisites {
|
||||
);
|
||||
}
|
||||
|
||||
$storageDirs = array(
|
||||
'storage',
|
||||
'storage/db',
|
||||
'storage/logs',
|
||||
'storage/upload',
|
||||
'storage/upload/css'
|
||||
$storageDirs = array_merge(
|
||||
array('storage'),
|
||||
$this->storageSubdirs
|
||||
);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
// Only check directories that exist - missing ones are handled in application validation
|
||||
if (is_dir($path)) {
|
||||
$writable = is_writable($path);
|
||||
$permissions = substr(sprintf('%o', fileperms($path)), -4);
|
||||
|
||||
@ -242,10 +225,71 @@ class Prerequisites {
|
||||
$allWritable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $allWritable;
|
||||
}
|
||||
|
||||
private function checkStorageDirectoriesExist() {
|
||||
$allPresent = true;
|
||||
foreach ($this->storageSubdirs as $dir) {
|
||||
$path = $this->baseDir . '/' . $dir;
|
||||
$exists = is_dir($path);
|
||||
|
||||
$this->addCheck(
|
||||
"Storage Directory: {$dir}",
|
||||
$exists,
|
||||
$exists ? "Present" : "Missing - will be created during setup",
|
||||
$exists ? 'info' : 'info' // Not an error - can be auto-created
|
||||
);
|
||||
|
||||
if (!$exists) {
|
||||
$allPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $allPresent;
|
||||
}
|
||||
|
||||
private function createStorageDirectories() {
|
||||
$storageDirs = array_merge(
|
||||
array('storage'),
|
||||
$this->storageSubdirs
|
||||
);
|
||||
|
||||
$allCreated = true;
|
||||
foreach ($storageDirs as $dir) {
|
||||
$path = $this->baseDir . '/' . $dir;
|
||||
|
||||
if (!is_dir($path)) {
|
||||
$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'
|
||||
);
|
||||
$allCreated = false;
|
||||
}
|
||||
} else {
|
||||
$this->addCheck(
|
||||
"Storage Directory: {$dir}",
|
||||
true,
|
||||
"Already exists"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $allCreated;
|
||||
}
|
||||
|
||||
private function checkWebServerConfig() {
|
||||
if ($this->isCli) {
|
||||
$this->addCheck(
|
||||
@ -283,74 +327,20 @@ class Prerequisites {
|
||||
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',
|
||||
'Database directory does not exist',
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$canCreateDb = is_writable($dbDir);
|
||||
$this->addCheck(
|
||||
@ -370,18 +360,8 @@ class Prerequisites {
|
||||
$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'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$canCreateDb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($dbReadable && $dbWritable) {
|
||||
// Test database connection
|
||||
try {
|
||||
$db = new PDO("sqlite:" . $dbFile);
|
||||
@ -400,8 +380,54 @@ class Prerequisites {
|
||||
// Store working database connection
|
||||
$this->database = $db;
|
||||
|
||||
// Test migrations
|
||||
return $this->checkMigrations($db);
|
||||
return true;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$this->addCheck(
|
||||
'Database Connection',
|
||||
false,
|
||||
'Failed to connect: ' . $e->getMessage(),
|
||||
'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->addCheck(
|
||||
'Database File',
|
||||
$canCreateDb,
|
||||
$canCreateDb ? 'Will be created during setup' : 'Cannot create - directory not writable',
|
||||
$canCreateDb ? 'info' : 'error'
|
||||
);
|
||||
return $canCreateDb;
|
||||
}
|
||||
}
|
||||
|
||||
private function createDatabase() {
|
||||
$dbFile = $this->baseDir . '/storage/db/tkr.sqlite';
|
||||
|
||||
// Test database connection (will create file if needed)
|
||||
try {
|
||||
$db = new PDO("sqlite:" . $dbFile);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
|
||||
// Test basic query to ensure database is functional
|
||||
$db->query("SELECT 1")->fetchColumn();
|
||||
|
||||
$this->addCheck(
|
||||
'Database Connection',
|
||||
true,
|
||||
'Successfully connected to database'
|
||||
);
|
||||
|
||||
// Store working database connection
|
||||
$this->database = $db;
|
||||
|
||||
// Run migrations
|
||||
return $this->applyMigrations($db);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$this->addCheck(
|
||||
@ -414,7 +440,7 @@ class Prerequisites {
|
||||
}
|
||||
}
|
||||
|
||||
private function checkMigrations($db) {
|
||||
private function applyMigrations($db) {
|
||||
try {
|
||||
$migrator = new Migrator($db);
|
||||
$migrator->migrate();
|
||||
@ -437,23 +463,20 @@ class Prerequisites {
|
||||
}
|
||||
}
|
||||
|
||||
// validate prereqs
|
||||
// runs on each request and can be run from CLI
|
||||
public function validate(): bool {
|
||||
$this->log("=== tkr prerequisites check started at " . date('Y-m-d H:i:s') . " ===", true);
|
||||
// Validate system requirements that can't be fixed by the script
|
||||
public function validateSystem(): bool {
|
||||
$this->log("=== tkr system validation started at " . date('Y-m-d H:i:s') . " ===", true);
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->log("\n🔍 Validating prerequisites...\n");
|
||||
$this->log("\n🔍 Validating system requirements...\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()
|
||||
'existing_storage_permissions' => $this->checkExistingStoragePermissions(),
|
||||
'web_server' => $this->checkWebServerConfig()
|
||||
);
|
||||
|
||||
// Check recommended extensions too
|
||||
@ -467,6 +490,44 @@ class Prerequisites {
|
||||
return count($this->errors) === 0;
|
||||
}
|
||||
|
||||
// Validate application state - things that can be fixed
|
||||
public function validateApplication(): bool {
|
||||
$currentErrors = count($this->errors);
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->log("\n🔍 Validating application state...\n");
|
||||
}
|
||||
|
||||
$results = array(
|
||||
'storage_directories' => $this->checkStorageDirectoriesExist(),
|
||||
'database' => $this->checkDatabase()
|
||||
);
|
||||
|
||||
// Return true if no NEW errors occurred
|
||||
return count($this->errors) === $currentErrors;
|
||||
}
|
||||
|
||||
// Create missing application components
|
||||
public function createMissing(): bool {
|
||||
$this->log("=== tkr setup started at " . date('Y-m-d H:i:s') . " ===", true);
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->log("\n🚀 Creating missing components...\n");
|
||||
}
|
||||
|
||||
$results = array(
|
||||
'storage_setup' => $this->createStorageDirectories(),
|
||||
'database_setup' => $this->createDatabase()
|
||||
);
|
||||
|
||||
if ($this->isCli) {
|
||||
$this->generateCliSummary($results);
|
||||
}
|
||||
|
||||
// Return true only if no errors occurred
|
||||
return count($this->errors) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display web-friendly error page when minimum requirements aren't met
|
||||
*/
|
||||
|
@ -20,8 +20,8 @@ class Router {
|
||||
['logout', 'AuthController@handleLogout', ['GET', 'POST']],
|
||||
['mood', 'MoodController'],
|
||||
['mood', 'MoodController@handlePost', ['POST']],
|
||||
['setup', 'AdminController@showSetup'],
|
||||
['setup', 'AdminController@handleSetup', ['POST']],
|
||||
['tkr-setup', 'AdminController@showSetup'],
|
||||
['tkr-setup', 'AdminController@handleSetup', ['POST']],
|
||||
['tick/{id}', 'TickController'],
|
||||
['css/custom/{filename}.css', 'CssController@serveCustomCss'],
|
||||
];
|
||||
|
@ -103,4 +103,42 @@ class Util {
|
||||
|
||||
return $basePath . '/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-detect base URL and path from HTTP request headers
|
||||
* Returns array with baseUrl, basePath, and fullUrl
|
||||
*/
|
||||
public static function getAutodetectedUrl(): array {
|
||||
// Detect base URL
|
||||
$baseUrl = ($_SERVER['HTTPS'] ?? 'off') === 'on' ? 'https://' : 'http://';
|
||||
$baseUrl .= $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
|
||||
// Don't include standard ports in URL
|
||||
$port = $_SERVER['SERVER_PORT'] ?? null;
|
||||
if ($port && $port != 80 && $port != 443) {
|
||||
$baseUrl .= ':' . $port;
|
||||
}
|
||||
|
||||
// Detect base path from script location
|
||||
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
|
||||
$basePath = dirname($scriptName);
|
||||
|
||||
if ($basePath === '/' || $basePath === '.' || $basePath === '') {
|
||||
$basePath = '/';
|
||||
} else {
|
||||
$basePath = '/' . trim($basePath, '/') . '/';
|
||||
}
|
||||
|
||||
// Construct full URL
|
||||
$fullUrl = $baseUrl;
|
||||
if ($basePath !== '/') {
|
||||
$fullUrl .= ltrim($basePath, '/');
|
||||
}
|
||||
|
||||
return [
|
||||
'baseUrl' => $baseUrl,
|
||||
'basePath' => $basePath,
|
||||
'fullUrl' => rtrim($fullUrl, '/')
|
||||
];
|
||||
}
|
||||
}
|
@ -15,10 +15,7 @@ class ConfigModel {
|
||||
|
||||
// Instance method that uses injected database
|
||||
public function get(): self {
|
||||
$init = require APP_ROOT . '/config/init.php';
|
||||
$c = new self($this->db);
|
||||
$c->baseUrl = ($c->baseUrl === '') ? $init['base_url'] : $c->baseUrl;
|
||||
$c->basePath = ($c->basePath === '') ? $init['base_path'] : $c->basePath;
|
||||
|
||||
$stmt = $this->db->query("SELECT site_title,
|
||||
site_description,
|
||||
|
214
tkr-setup.php
Normal file
214
tkr-setup.php
Normal file
@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* tkr Setup Script
|
||||
*
|
||||
* Interactive CLI setup for tkr - run this once after installation
|
||||
* Usage: php tkr-setup.php [--validate-only]
|
||||
*/
|
||||
|
||||
// Ensure this is run from command line only
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check for validate-only flag
|
||||
$validateOnly = in_array('--validate-only', $argv);
|
||||
|
||||
// Load the bootstrap
|
||||
require_once __DIR__ . '/config/bootstrap.php';
|
||||
|
||||
if (!$validateOnly) {
|
||||
echo "🚀 Welcome to tkr Setup!\n";
|
||||
echo "This will configure your tkr installation.\n\n";
|
||||
}
|
||||
|
||||
// Check system requirements first
|
||||
$prerequisites = new Prerequisites();
|
||||
if (!$prerequisites->validateSystem()) {
|
||||
echo "\n❌ System requirements not met. Please resolve the issues above before continuing.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "✅ System requirements met\n\n";
|
||||
|
||||
// Check application state
|
||||
$applicationReady = $prerequisites->validateApplication();
|
||||
|
||||
if ($applicationReady) {
|
||||
echo "✅ All prerequisites satisfied - tkr is ready to run!\n";
|
||||
} else {
|
||||
echo "⚠️ Application components need to be created\n\n";
|
||||
if ($validateOnly) {
|
||||
echo "⚠️ Run 'php tkr-setup.php' (without --validate-only) to complete setup.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// If validate-only flag, exit here
|
||||
if ($validateOnly) {
|
||||
// Always exit with success.
|
||||
// If app configuration needs to be completed, the script can handle that.
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Continue with setup process
|
||||
$db = null;
|
||||
try {
|
||||
if ($applicationReady) {
|
||||
$db = $prerequisites->getDatabase();
|
||||
|
||||
// Check if user already exists
|
||||
$userCount = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
||||
if ($userCount > 0) {
|
||||
echo "⚠️ tkr appears to already be set up.\n";
|
||||
echo "Continue anyway? (y/N): ";
|
||||
$continue = trim(fgets(STDIN));
|
||||
if (strtolower($continue) !== 'y') {
|
||||
echo "Setup cancelled.\n";
|
||||
exit(0);
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Application not ready - will create below
|
||||
}
|
||||
|
||||
// If application isn't ready, create missing components
|
||||
if (!$db) {
|
||||
echo "Setting up application components...\n";
|
||||
if (!$prerequisites->createMissing()) {
|
||||
echo "❌ Failed to create application components. Check the errors above.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$db = $prerequisites->getDatabase();
|
||||
echo "✅ Application components created\n\n";
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Failed to get database connection: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt for configuration
|
||||
echo "📝 Please provide the following information:\n\n";
|
||||
|
||||
// 1. Site URL (with auto-detect option)
|
||||
echo "1. Site URL (including base path if not root)\n";
|
||||
echo " Examples: https://example.com or https://example.com/tkr\n";
|
||||
echo " Leave blank to auto-detect from first web request\n";
|
||||
echo " Site URL (optional): ";
|
||||
$siteUrl = trim(fgets(STDIN));
|
||||
|
||||
if (empty($siteUrl)) {
|
||||
echo "✅ Will auto-detect URL on first web request\n";
|
||||
$baseUrl = '';
|
||||
$basePath = '';
|
||||
} else {
|
||||
// Parse URL to extract base URL and base path
|
||||
$parsedUrl = parse_url($siteUrl);
|
||||
if (!$parsedUrl || !isset($parsedUrl['scheme']) || !isset($parsedUrl['host'])) {
|
||||
echo "❌ Invalid URL format\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Validate host for basic security
|
||||
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $parsedUrl['host'])) {
|
||||
echo "❌ Invalid characters in hostname\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
|
||||
if (isset($parsedUrl['port']) && $parsedUrl['port'] != 80 && $parsedUrl['port'] != 443) {
|
||||
$baseUrl .= ':' . $parsedUrl['port'];
|
||||
}
|
||||
|
||||
$basePath = isset($parsedUrl['path']) ? rtrim($parsedUrl['path'], '/') : '';
|
||||
if (empty($basePath)) {
|
||||
$basePath = '/';
|
||||
} else {
|
||||
$basePath = '/' . trim($basePath, '/') . '/';
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// 2. Admin credentials
|
||||
echo "2. Admin username: ";
|
||||
$adminUsername = trim(fgets(STDIN));
|
||||
|
||||
if (empty($adminUsername)) {
|
||||
echo "❌ Admin username is required\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "3. Admin password: ";
|
||||
// Hide password input on Unix systems
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
system('stty -echo');
|
||||
}
|
||||
$adminPassword = trim(fgets(STDIN));
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
system('stty echo');
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
if (empty($adminPassword)) {
|
||||
echo "❌ Admin password is required\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n4. Site title (optional, default: 'My tkr Site'): ";
|
||||
$siteTitle = trim(fgets(STDIN));
|
||||
if (empty($siteTitle)) {
|
||||
$siteTitle = 'My tkr Site';
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Save configuration
|
||||
try {
|
||||
echo "💾 Saving configuration...\n";
|
||||
|
||||
// Create/update settings
|
||||
$configModel = new ConfigModel($db);
|
||||
$configModel->siteTitle = $siteTitle;
|
||||
$configModel->baseUrl = $baseUrl;
|
||||
$configModel->basePath = $basePath;
|
||||
$config = $configModel->save();
|
||||
|
||||
// Create admin user
|
||||
$userModel = new UserModel($db);
|
||||
$userModel->username = $adminUsername;
|
||||
$userModel->display_name = $adminUsername;
|
||||
$userModel->website = '';
|
||||
$userModel->mood = '';
|
||||
$user = $userModel->save();
|
||||
|
||||
// Set admin password
|
||||
$userModel->setPassword($adminPassword);
|
||||
|
||||
echo "✅ Configuration saved\n";
|
||||
echo "✅ Admin user created\n\n";
|
||||
|
||||
echo "🎉 Setup complete!\n\n";
|
||||
|
||||
if (!empty($baseUrl)) {
|
||||
echo "Your tkr site is ready at: $siteUrl\n";
|
||||
} else {
|
||||
echo "Your tkr site will be ready after you visit it in a web browser\n";
|
||||
echo "The URL will be auto-detected on first access\n";
|
||||
}
|
||||
|
||||
echo "Login with username: $adminUsername\n\n";
|
||||
echo "You can now:\n";
|
||||
echo "• Point your web server document root to the 'public/' directory\n";
|
||||
echo "• Visit your site and log in\n";
|
||||
echo "• Customize additional settings through the admin interface\n\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Setup failed: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user