Fix first-time setup issues. (#68)
Fixes for issues found testing first time setup in the different configurations. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/68 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
parent
801bbebf4f
commit
d3a537aa6c
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,9 +14,10 @@ storage/logs
|
||||
# Testing stuff
|
||||
/docker-compose.yml
|
||||
scratch
|
||||
storage.bak
|
||||
|
||||
# Build artifacts
|
||||
tkr.tgz
|
||||
|
||||
# Test logs
|
||||
storage/prerequisite-check.log
|
||||
storage/prerequisite-check.log
|
||||
|
@ -5,6 +5,10 @@ declare(strict_types=1);
|
||||
// - define paths
|
||||
// - set up autoloader
|
||||
|
||||
// Set a couple ini settings for security
|
||||
ini_set('allow_url_fopen', 0); // don't allow remote files to be read
|
||||
ini_set('expose_php', 0); // don't advertise the PHP version
|
||||
|
||||
// Define all the important paths
|
||||
define('APP_ROOT', dirname(dirname(__FILE__)));
|
||||
// Root-level directories
|
||||
|
@ -13,6 +13,9 @@ RewriteRule ^(storage|src|templates|config)(/.*)?$ - [F,L]
|
||||
# Block access to hidden files
|
||||
RewriteRule ^\..*$ - [F,L]
|
||||
|
||||
# Block access to setup script
|
||||
RewriteRule ^tkr-setup\.php$ - [F,L]
|
||||
|
||||
# Route everything else through public/index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
@ -10,6 +10,7 @@ services:
|
||||
- ./src:/var/www/html/tkr/src
|
||||
- ./storage:/var/www/html/tkr/storage
|
||||
- ./templates:/var/www/html/tkr/templates
|
||||
- ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php
|
||||
- ./docker/apache/shared-hosting/.htaccess:/var/www/html/tkr/.htaccess
|
||||
command: >
|
||||
bash -c "a2enmod rewrite headers expires &&
|
||||
|
@ -10,6 +10,7 @@ services:
|
||||
- ./src:/var/www/tkr/src
|
||||
- ./storage:/var/www/tkr/storage
|
||||
- ./templates:/var/www/tkr/templates
|
||||
- ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php
|
||||
- ./docker/apache/vps/root/tkr.my-domain.com.conf:/etc/apache2/sites-enabled/tkr.my-domain.com.conf
|
||||
command: >
|
||||
bash -c "a2enmod rewrite headers expires &&
|
||||
|
@ -10,6 +10,7 @@ services:
|
||||
- ./src:/var/www/tkr/src
|
||||
- ./storage:/var/www/tkr/storage
|
||||
- ./templates:/var/www/tkr/templates
|
||||
- ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php
|
||||
- ./docker/apache/vps/subfolder/my-domain.com.conf:/etc/apache2/sites-enabled/my-domain.com.conf
|
||||
command: >
|
||||
bash -c "a2enmod rewrite headers expires &&
|
||||
|
@ -20,6 +20,7 @@ services:
|
||||
- ./src:/var/www/tkr/src
|
||||
- ./storage:/var/www/tkr/storage
|
||||
- ./templates:/var/www/tkr/templates
|
||||
- ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php
|
||||
command: >
|
||||
sh -c "
|
||||
chown -R www-data:www-data /var/www/tkr/storage &&
|
||||
|
@ -20,6 +20,7 @@ services:
|
||||
- ./src:/var/www/tkr/src
|
||||
- ./storage:/var/www/tkr/storage
|
||||
- ./templates:/var/www/tkr/templates
|
||||
- ./tkr-setup.php:/var/www/html/tkr/tkr-setup.php
|
||||
command: >
|
||||
sh -c "
|
||||
chown -R www-data:www-data /var/www/tkr/storage &&
|
||||
|
@ -14,6 +14,9 @@ RewriteRule ^(storage|src|templates|config)(/.*)?$ - [F,L]
|
||||
# Block access to hidden files
|
||||
RewriteRule ^\..*$ - [F,L]
|
||||
|
||||
# Block access to setup script
|
||||
RewriteRule ^tkr-setup\.php$ - [F,L]
|
||||
|
||||
# Route everything else through the front controller
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
@ -49,7 +49,8 @@ if (!$prerequisites->applyMigrations($db)){
|
||||
}
|
||||
|
||||
// Check if setup is complete (user exists and URL is configured)
|
||||
if (!(preg_match('/tkr-setup$/', $path))) {
|
||||
// Skip the setup check for the default css
|
||||
if (!(preg_match('/tkr-setup$/', $path) || preg_match('/default.css$/', $path))) {
|
||||
try {
|
||||
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
||||
$settings = (new SettingsModel($db))->get();
|
||||
@ -72,6 +73,10 @@ if (!(preg_match('/tkr-setup$/', $path))) {
|
||||
echo "<p>Please check your installation or contact your hosting provider.</p>";
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// we're heading to setup. the base path hasn't been set. autodetect it
|
||||
$autodetected = Util::getAutodetectedUrl();
|
||||
$basePath = $autodetected['basePath'];
|
||||
}
|
||||
|
||||
/*
|
||||
@ -92,8 +97,11 @@ Session::start();
|
||||
Session::generateCsrfToken();
|
||||
|
||||
// Remove the base path from the URL
|
||||
if (strpos($path, $app['settings']->basePath) === 0) {
|
||||
$path = substr($path, strlen($app['settings']->basePath));
|
||||
// If basePath isn't already set (i.e. we're not autodetecting it en route to tkr-setup),
|
||||
// set it to the value from settings
|
||||
$basePath ??= $app['settings']->basePath;
|
||||
if (strpos($path, $basePath) === 0) {
|
||||
$path = substr($path, strlen($basePath));
|
||||
}
|
||||
|
||||
// strip the trailing slash from the resulting route
|
||||
@ -106,7 +114,7 @@ Log::debug("Path requested: {$path}");
|
||||
// if this is a POST and we aren't in setup,
|
||||
// make sure there's a valid session
|
||||
// if not, redirect to /login or die as appropriate
|
||||
if ($method === 'POST' && $path != 'setup') {
|
||||
if ($method === 'POST' && $path != 'tkr-setup') {
|
||||
if ($path != 'login'){
|
||||
if (!Session::isValid($_POST['csrf_token'])) {
|
||||
// Invalid session - redirect to /login
|
||||
|
@ -100,7 +100,7 @@ class CssController extends Controller {
|
||||
}
|
||||
|
||||
// Get the data for the selected CSS file
|
||||
$cssId = $_POST['selectCssFile'];
|
||||
$cssId = (int) $_POST['selectCssFile'];
|
||||
$cssModel = new CssModel($app['db']);
|
||||
$cssRow = $cssModel->getById($cssId);
|
||||
|
||||
|
@ -407,7 +407,7 @@ class Prerequisites {
|
||||
}
|
||||
}
|
||||
|
||||
private function createDatabase() {
|
||||
private function createDatabase(): bool {
|
||||
$dbFile = $this->baseDir . '/storage/db/tkr.sqlite';
|
||||
|
||||
// Test database connection (will create file if needed)
|
||||
@ -442,7 +442,7 @@ class Prerequisites {
|
||||
}
|
||||
}
|
||||
|
||||
public function applyMigrations($db) {
|
||||
public function applyMigrations($db): bool {
|
||||
try {
|
||||
$migrator = new Migrator($db);
|
||||
$migrator->migrate();
|
||||
@ -452,8 +452,6 @@ class Prerequisites {
|
||||
true,
|
||||
'All database migrations applied successfully'
|
||||
);
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->addCheck(
|
||||
'Database Migrations',
|
||||
@ -463,6 +461,8 @@ class Prerequisites {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate system requirements that can't be fixed by the script
|
||||
@ -511,6 +511,8 @@ class Prerequisites {
|
||||
|
||||
// Create missing application components
|
||||
public function createMissing(): bool {
|
||||
// If we're calling this, there were likely setup validation errors
|
||||
$currentErrors = count($this->errors);
|
||||
$this->log("=== tkr setup started at " . date('Y-m-d H:i:s') . " ===", true);
|
||||
|
||||
if ($this->isCli) {
|
||||
@ -526,8 +528,8 @@ class Prerequisites {
|
||||
$this->generateCliSummary($results);
|
||||
}
|
||||
|
||||
// Return true only if no errors occurred
|
||||
return count($this->errors) === 0;
|
||||
// Return true only if no NEW errors occurred
|
||||
return count($this->errors) === $currentErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,15 @@ class Session {
|
||||
// global $_SESSION associative array
|
||||
public static function start(): void{
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
// Cookie security settings
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.cookie_samesite', 'Strict');
|
||||
|
||||
// Enable secure cookie flag if HTTPS is being used
|
||||
if (($_SERVER['HTTPS'] ?? 'off') === 'on') {
|
||||
ini_set('session.cookie_secure', 1);
|
||||
}
|
||||
|
||||
$existingSessionId = $_COOKIE['PHPSESSID'] ?? null;
|
||||
session_start();
|
||||
|
||||
|
@ -125,6 +125,13 @@ class Util {
|
||||
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
|
||||
$basePath = dirname($scriptName);
|
||||
|
||||
// Handle shared hosting scenario where document root can't be set to public/
|
||||
// If script name ends with /public/index.php, we need to go up one directory
|
||||
if (str_ends_with($scriptName, '/public/index.php')) {
|
||||
$basePath = dirname($basePath);
|
||||
}
|
||||
|
||||
# Ensure base path always has a trailing /
|
||||
if ($basePath === '/' || $basePath === '.' || $basePath === '') {
|
||||
$basePath = '/';
|
||||
} else {
|
||||
@ -132,10 +139,7 @@ class Util {
|
||||
}
|
||||
|
||||
// Construct full URL
|
||||
$fullUrl = $baseUrl;
|
||||
if ($basePath !== '/') {
|
||||
$fullUrl .= ltrim($basePath, '/');
|
||||
}
|
||||
$fullUrl = $baseUrl . $basePath;
|
||||
|
||||
return [
|
||||
'baseUrl' => $baseUrl,
|
||||
|
@ -1,12 +1,16 @@
|
||||
<?php /** @var SettingsModel $settings */ ?>
|
||||
<?php /** @var UserModel $user */ ?>
|
||||
<?php /** @var isSetup bool */ ?>
|
||||
<h1><?php if ($isSetup): ?>Setup<?php else: ?>Admin<?php endif; ?></h1>
|
||||
<?php
|
||||
$title = $isSetup ? 'Setup' : 'Admin';
|
||||
$urlPath = $isSetup ? 'tkr-setup' : 'admin'
|
||||
?>
|
||||
<h1><?php echo $title ?></h1>
|
||||
<main>
|
||||
<form
|
||||
action="<?php echo Util::buildRelativeUrl($settings->basePath, ($isSetup ? 'setup' : 'admin')) ?>"
|
||||
action="<?php echo Util::buildRelativeUrl($settings->basePath, $urlPath) ?>"
|
||||
method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||
<fieldset>
|
||||
<legend>User settings</legend>
|
||||
<div class="fieldset-items">
|
||||
@ -14,19 +18,19 @@
|
||||
<input type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
value="<?= Util::escape_html($user->username) ?>"
|
||||
value="<?php echo Util::escape_html($user->username) ?>"
|
||||
required>
|
||||
<label for="display_name">Display name <span class=required>*</span></label>
|
||||
<input type="text"
|
||||
id="display_name"
|
||||
name="display_name"
|
||||
value="<?= Util::escape_html($user->displayName) ?>"
|
||||
value="<?php echo Util::escape_html($user->displayName) ?>"
|
||||
required>
|
||||
<label for="website">Website </label>
|
||||
<input type="text"
|
||||
id="website"
|
||||
name="website"
|
||||
value="<?= Util::escape_html($user->website) ?>">
|
||||
value="<?php echo Util::escape_html($user->website) ?>">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@ -36,36 +40,36 @@
|
||||
<input type="text"
|
||||
id="site_title"
|
||||
name="site_title"
|
||||
value="<?= Util::escape_html($settings->siteTitle) ?>"
|
||||
value="<?php echo Util::escape_html($settings->siteTitle) ?>"
|
||||
required>
|
||||
<label for="site_description">Description <span class=required>*</span></label>
|
||||
<input type="text"
|
||||
id="site_description"
|
||||
name="site_description"
|
||||
value="<?= Util::escape_html($settings->siteDescription) ?>">
|
||||
value="<?php echo Util::escape_html($settings->siteDescription) ?>">
|
||||
<label for="base_url">Base URL <span class=required>*</span></label>
|
||||
<input type="text"
|
||||
id="base_url"
|
||||
name="base_url"
|
||||
value="<?= Util::escape_html($settings->baseUrl) ?>"
|
||||
value="<?php echo Util::escape_html($settings->baseUrl) ?>"
|
||||
required>
|
||||
<label for="base_path">Base path <span class=required>*</span></label>
|
||||
<input type="text"
|
||||
id="base_path"
|
||||
name="base_path"
|
||||
value="<?= Util::escape_html($settings->basePath) ?>"
|
||||
value="<?php echo Util::escape_html($settings->basePath) ?>"
|
||||
required>
|
||||
<label for="items_per_page">Ticks per page (max 50) <span class=required>*</span></label>
|
||||
<input type="number"
|
||||
id="items_per_page"
|
||||
name="items_per_page"
|
||||
value="<?= $settings->itemsPerPage ?>" min="1" max="50"
|
||||
value="<?php echo $settings->itemsPerPage ?>" min="1" max="50"
|
||||
required>
|
||||
<label for="tick_delete_hours">Tick delete window (hours)</label>
|
||||
<input type="number"
|
||||
id="tick_delete_hours"
|
||||
name="tick_delete_hours"
|
||||
value="<?= ($settings->tickDeleteHours ?? 1) ?>" min="1">
|
||||
value="<?php echo ($settings->tickDeleteHours ?? 1) ?>" min="1">
|
||||
<label for="strict_accessibility">Strict accessibility</label>
|
||||
<input type="checkbox"
|
||||
id="strict_accessibility"
|
||||
@ -74,10 +78,10 @@
|
||||
<?php if ($settings->strictAccessibility): ?> checked <?php endif; ?>>
|
||||
<label for="log_level">Log Level</label>
|
||||
<select id="log_level" name="log_level">
|
||||
<option value="1" <?= ($settings->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
|
||||
<option value="2" <?= ($settings->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
|
||||
<option value="3" <?= ($settings->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
|
||||
<option value="4" <?= ($settings->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
|
||||
<option value="1" <?php echo ($settings->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
|
||||
<option value="2" <?php echo ($settings->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
|
||||
<option value="3" <?php echo ($settings->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
|
||||
<option value="4" <?php echo ($settings->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -177,6 +177,7 @@ try {
|
||||
// Create/update settings
|
||||
$settingsModel = new SettingsModel($db);
|
||||
$settingsModel->siteTitle = $siteTitle;
|
||||
$settingsModel->siteDescription = $siteTitle;
|
||||
$settingsModel->baseUrl = $baseUrl;
|
||||
$settingsModel->basePath = $basePath;
|
||||
$settings = $settingsModel->save();
|
||||
@ -184,7 +185,7 @@ try {
|
||||
// Create admin user
|
||||
$userModel = new UserModel($db);
|
||||
$userModel->username = $adminUsername;
|
||||
$userModel->display_name = $adminUsername;
|
||||
$userModel->displayName = $adminUsername;
|
||||
$userModel->website = '';
|
||||
$userModel->mood = '';
|
||||
$user = $userModel->save();
|
||||
|
Loading…
x
Reference in New Issue
Block a user