feat: Add complete Docker deployment with web-based setup wizard
Major additions: - Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js) - Production Docker configuration (docker-compose.prod.yml, .env.production) - Database initialization SQL files (deploy/init_settings.sql) - Template builder system with drag-and-drop UI - Advanced features (OAuth, CDN, enhanced analytics, monetization) - Comprehensive documentation (deployment guides, quick start, feature docs) - Design system with accessibility and responsive layout - Deployment automation scripts (deploy.ps1, generate-secrets.ps1) Setup wizard allows customization of: - Platform name and branding - Domain configuration - Membership tiers and pricing - Admin credentials - Feature toggles Database includes 270+ tables for complete video streaming platform with advanced features for analytics, moderation, template building, and monetization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
305
setup_wizard.php
Normal file
305
setup_wizard.php
Normal file
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream - Setup Wizard Backend
|
||||
| Software Description : Handles setup wizard logic and database configuration
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
class SetupWizard {
|
||||
private $pdo;
|
||||
private $config;
|
||||
|
||||
public function __construct() {
|
||||
$this->loadDatabaseConnection();
|
||||
$this->config = [];
|
||||
}
|
||||
|
||||
private function loadDatabaseConnection() {
|
||||
try {
|
||||
$db_host = getenv('DB_HOST') ?: 'db';
|
||||
$db_name = getenv('DB_NAME') ?: 'easystream';
|
||||
$db_user = getenv('DB_USER') ?: 'easystream';
|
||||
$db_pass = getenv('DB_PASS') ?: 'easystream';
|
||||
|
||||
$this->pdo = new PDO(
|
||||
"mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
|
||||
$db_user,
|
||||
$db_pass,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Setup Wizard DB Connection Error: " . $e->getMessage());
|
||||
throw new Exception("Database connection failed. Please check your configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
public function testDatabaseConnection($data) {
|
||||
try {
|
||||
// Test if tables exist
|
||||
$stmt = $this->pdo->query("SHOW TABLES");
|
||||
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$tableCount = count($tables);
|
||||
|
||||
// Check if db_settings table exists
|
||||
$hasSettings = in_array('db_settings', $tables);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "Database connected successfully",
|
||||
'tableCount' => $tableCount,
|
||||
'hasSettings' => $hasSettings
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function saveConfiguration($data) {
|
||||
try {
|
||||
// Validate required fields
|
||||
$required = ['platformName', 'domainName', 'contactEmail'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
throw new Exception("Missing required field: $field");
|
||||
}
|
||||
}
|
||||
|
||||
// Save configuration to database
|
||||
$settings = [
|
||||
'site_name' => $data['platformName'],
|
||||
'site_tagline' => $data['platformTagline'] ?? '',
|
||||
'site_url' => $data['domainName'],
|
||||
'site_email' => $data['contactEmail'],
|
||||
'site_timezone' => $data['timezone'] ?? 'UTC',
|
||||
|
||||
// Branding
|
||||
'theme_primary_color' => $data['primaryColor'] ?? '#667eea',
|
||||
'theme_secondary_color' => $data['secondaryColor'] ?? '#764ba2',
|
||||
'theme_default' => $data['defaultTheme'] ?? 'light',
|
||||
'theme_allow_switching' => !empty($data['enableTheming']) ? '1' : '0',
|
||||
|
||||
// Membership tiers
|
||||
'tier_free_name' => $data['tier1Name'] ?? 'Free',
|
||||
'tier_free_upload_limit' => $data['tier1Upload'] ?? 100,
|
||||
'tier_free_storage_limit' => $data['tier1Storage'] ?? 5,
|
||||
|
||||
'tier_premium_name' => $data['tier2Name'] ?? 'Premium',
|
||||
'tier_premium_upload_limit' => $data['tier2Upload'] ?? 500,
|
||||
'tier_premium_storage_limit' => $data['tier2Storage'] ?? 50,
|
||||
'tier_premium_price' => $data['tier2Price'] ?? 9.99,
|
||||
|
||||
'tier_enterprise_name' => $data['tier3Name'] ?? 'Enterprise',
|
||||
'tier_enterprise_upload_limit' => $data['tier3Upload'] ?? 2048,
|
||||
'tier_enterprise_storage_limit' => $data['tier3Storage'] ?? 500,
|
||||
'tier_enterprise_price' => $data['tier3Price'] ?? 49.99,
|
||||
|
||||
// Features
|
||||
'feature_registration' => !empty($data['enableRegistration']) ? '1' : '0',
|
||||
'feature_email_verification' => !empty($data['enableEmailVerification']) ? '1' : '0',
|
||||
'feature_live_streaming' => !empty($data['enableLiveStreaming']) ? '1' : '0',
|
||||
'feature_comments' => !empty($data['enableComments']) ? '1' : '0',
|
||||
'feature_downloads' => !empty($data['enableDownloads']) ? '1' : '0',
|
||||
'feature_monetization' => !empty($data['enableMonetization']) ? '1' : '0',
|
||||
'feature_template_builder' => !empty($data['enableTemplateBuilder']) ? '1' : '0',
|
||||
'feature_analytics' => !empty($data['enableAnalytics']) ? '1' : '0',
|
||||
|
||||
// Meta
|
||||
'setup_completed' => '1',
|
||||
'setup_date' => date('Y-m-d H:i:s'),
|
||||
'setup_version' => '2.0'
|
||||
];
|
||||
|
||||
// Check if db_settings table exists
|
||||
$stmt = $this->pdo->query("SHOW TABLES LIKE 'db_settings'");
|
||||
if ($stmt->rowCount() === 0) {
|
||||
// Table doesn't exist yet, create it
|
||||
$this->createSettingsTable();
|
||||
}
|
||||
|
||||
// Insert or update settings
|
||||
foreach ($settings as $key => $value) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO db_settings (cfg_name, cfg_value)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE cfg_value = VALUES(cfg_value)
|
||||
");
|
||||
$stmt->execute([$key, $value]);
|
||||
}
|
||||
|
||||
// Also write to config file for quick access
|
||||
$this->writeConfigFile($settings);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Configuration saved successfully'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
error_log("Setup Wizard Save Config Error: " . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function createAdminUser($data) {
|
||||
try {
|
||||
// Validate required fields
|
||||
if (empty($data['adminUsername']) || empty($data['adminEmail']) || empty($data['adminPassword'])) {
|
||||
throw new Exception("All admin fields are required");
|
||||
}
|
||||
|
||||
// Validate password match
|
||||
if ($data['adminPassword'] !== $data['adminPasswordConfirm']) {
|
||||
throw new Exception("Passwords do not match");
|
||||
}
|
||||
|
||||
// Validate password strength
|
||||
if (strlen($data['adminPassword']) < 8) {
|
||||
throw new Exception("Password must be at least 8 characters long");
|
||||
}
|
||||
|
||||
// Hash password
|
||||
$passwordHash = password_hash($data['adminPassword'], PASSWORD_BCRYPT);
|
||||
|
||||
// Check if db_accountuser table exists
|
||||
$stmt = $this->pdo->query("SHOW TABLES LIKE 'db_accountuser'");
|
||||
if ($stmt->rowCount() === 0) {
|
||||
throw new Exception("User table not found. Database may not be properly initialized.");
|
||||
}
|
||||
|
||||
// Check if admin already exists
|
||||
$stmt = $this->pdo->prepare("SELECT usr_id FROM db_accountuser WHERE usr_user = :username OR usr_email = :email");
|
||||
$stmt->execute([
|
||||
'username' => $data['adminUsername'],
|
||||
'email' => $data['adminEmail']
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Update existing admin
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE db_accountuser
|
||||
SET usr_password = :password,
|
||||
usr_email = :email,
|
||||
usr_dname = :displayname,
|
||||
usr_role = 'admin',
|
||||
usr_status = 1,
|
||||
usr_verified = 1
|
||||
WHERE usr_user = :username OR usr_email = :email
|
||||
");
|
||||
} else {
|
||||
// Insert new admin - use SET sql_mode to allow more flexible inserts
|
||||
$this->pdo->exec("SET sql_mode=''");
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO db_accountuser (
|
||||
usr_key, usr_user, usr_password, usr_email, usr_dname, usr_role, usr_status, usr_verified,
|
||||
usr_IP, usr_logins, usr_lastlogin, usr_joindate, live_key
|
||||
) VALUES (
|
||||
1, :username, :password, :email, :displayname, 'admin', 1, 1,
|
||||
'127.0.0.1', 0, NOW(), NOW(), ''
|
||||
)
|
||||
");
|
||||
}
|
||||
|
||||
$stmt->execute([
|
||||
'username' => $data['adminUsername'],
|
||||
'password' => $passwordHash,
|
||||
'email' => $data['adminEmail'],
|
||||
'displayname' => $data['adminDisplayName'] ?? 'Administrator'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Admin user created successfully'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
error_log("Setup Wizard Create Admin Error: " . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function finalizeSetup($data) {
|
||||
try {
|
||||
// Create .setup_complete file to prevent re-running setup
|
||||
file_put_contents('.setup_complete', json_encode([
|
||||
'completed_at' => date('Y-m-d H:i:s'),
|
||||
'platform_name' => $data['platformName'] ?? 'EasyStream',
|
||||
'domain' => $data['domainName'] ?? 'localhost',
|
||||
'version' => '2.0'
|
||||
]));
|
||||
|
||||
// Update Caddyfile with domain
|
||||
if (!empty($data['domainName']) && $data['domainName'] !== 'localhost') {
|
||||
$this->updateCaddyfile($data['domainName']);
|
||||
}
|
||||
|
||||
// Clear any cached config
|
||||
if (function_exists('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Setup completed successfully',
|
||||
'redirect' => '/'
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
error_log("Setup Wizard Finalize Error: " . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function createSettingsTable() {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `db_settings` (
|
||||
`cfg_name` VARCHAR(100) NOT NULL PRIMARY KEY,
|
||||
`cfg_value` TEXT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
private function writeConfigFile($settings) {
|
||||
$configContent = "<?php\n";
|
||||
$configContent .= "// Auto-generated setup configuration\n";
|
||||
$configContent .= "// Generated on " . date('Y-m-d H:i:s') . "\n\n";
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$safeValue = addslashes($value);
|
||||
$constantName = 'SETUP_' . strtoupper($key);
|
||||
$configContent .= "define('$constantName', '$safeValue');\n";
|
||||
}
|
||||
|
||||
// Write to f_core/config.setup.php
|
||||
$configFile = __DIR__ . '/f_core/config.setup.php';
|
||||
@file_put_contents($configFile, $configContent);
|
||||
}
|
||||
|
||||
private function updateCaddyfile($domain) {
|
||||
$caddyfile = __DIR__ . '/Caddyfile';
|
||||
|
||||
if (file_exists($caddyfile)) {
|
||||
$content = @file_get_contents($caddyfile);
|
||||
|
||||
// Replace localhost with actual domain
|
||||
$content = preg_replace('/http:\/\/localhost:\d+/', "https://$domain", $content);
|
||||
$content = preg_replace('/^:80\s*\{/', "$domain {\n encode gzip", $content);
|
||||
|
||||
@file_put_contents($caddyfile, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user