/
mibazarow
/
dev-web-server
Обзор
Документация
Войти
/
mibazarow
/
dev-web-server
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
install.py
1 069 строк
46 KB
Mihail Bazarow
fix php vers
25 авг 2025, 20:02
25 авг 2025, 20:02
524484d
Код
Авторство
О чём код?
#!/usr/bin/env python3 """ Web Server Installer and Site Manager Устанавливает Apache, Nginx, PHP, MariaDB и предоставляет интерфейс для управления сайтами """ import os import sys import subprocess import json import shutil import pwd import grp import secrets import string from pathlib import Path import argparse class WebServerInstaller: def __init__(self): self.config_file = f'/home/{os.getenv("USER")}/.web-server-installer.json' self.sites_config = self.load_config() self.current_user = os.getenv("USER") self.current_uid = os.getuid() self.current_gid = os.getgid() self.user_group = grp.getgrgid(self.current_gid).gr_name def load_config(self): """Загружает конфигурацию сайтов""" if os.path.exists(self.config_file): with open(self.config_file, 'r') as f: return json.load(f) return {'sites': []} def save_config(self): """Сохраняет конфигурацию сайтов""" with open(self.config_file, 'w') as f: json.dump(self.sites_config, f, indent=2) def run_command(self, command, check=True): """Выполняет команду в терминале""" print(f"Выполняется: {command}") try: result = subprocess.run(command, shell=True, check=check, capture_output=True, text=True) if result.stdout: print(result.stdout) return result.returncode == 0 except subprocess.CalledProcessError as e: print(f"Ошибка выполнения команды: {e}") if e.stderr: print(f"Stderr: {e.stderr}") return False def check_installed(self, package): """Проверяет, установлен ли пакет""" result = subprocess.run(f"which {package}", shell=True, capture_output=True, text=True) return result.returncode == 0 def check_debian_ubuntu(self): """Проверяет, что система основана на Debian/Ubuntu""" if not self.check_installed('apt'): raise Exception("❌ Эта программа работает только с Debian/Ubuntu и их производными") return True def get_default_php_version(self): """Определяет доступную версию PHP в стандартных репозиториях""" try: # Проверяем доступные пакеты PHP result = subprocess.run( "apt-cache search '^php[0-9]\\.[0-9]$' | head -1 | grep -o 'php[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): # Извлекаем версию (например, php8.1 -> 8.1) php_package = result.stdout.strip() version = php_package.replace('php', '') print(f"🔍 Найдена версия PHP: {version}") return version # Если не найдено, пробуем поиск по-другому result = subprocess.run( "apt-cache search '^php[0-9]\\.[0-9]-' | head -1 | grep -o 'php[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): php_package = result.stdout.strip() version = php_package.replace('php', '') print(f"🔍 Найдена версия PHP: {version}") return version # Попробуем еще один способ - проверка пакета php result = subprocess.run( "apt-cache show php 2>/dev/null | grep '^Depends:' | grep -o 'php[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): php_package = result.stdout.strip() version = php_package.replace('php', '') print(f"🔍 Найдена версия PHP через зависимости: {version}") return version # Если ничего не найдено, проверяем что уже установлено result = subprocess.run( "php --version 2>/dev/null | head -1 | grep -o '[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): version = result.stdout.strip() print(f"🔍 PHP уже установлен, версия: {version}") return version print("⚠️ Не удалось определить версию PHP, попробуем установить стандартный пакет 'php'") return None except Exception as e: print(f"❌ Ошибка при определении версии PHP: {e}") return None def get_installed_php_version(self): """Определяет установленную версию PHP""" try: # Проверяем установленную версию через php --version result = subprocess.run( "php --version 2>/dev/null | head -1 | grep -o '[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): version = result.stdout.strip() return version # Если не получается через php, ищем в установленных пакетах result = subprocess.run( "dpkg -l | grep '^ii' | grep 'php[0-9]\\.[0-9]' | head -1 | grep -o 'php[0-9]\\.[0-9]'", shell=True, capture_output=True, text=True ) if result.returncode == 0 and result.stdout.strip(): php_package = result.stdout.strip() version = php_package.replace('php', '') return version return None except Exception as e: print(f"❌ Ошибка при определении установленной версии PHP: {e}") return None def install_packages(self): """Устанавливает все необходимые пакеты""" print("🚀 Начинается установка веб-сервера...") # Проверяем, что система поддерживается self.check_debian_ubuntu() print("✅ Система совместима (Debian/Ubuntu)") # Базовые пакеты для установки packages = [ 'apache2', 'nginx', 'mariadb-server', 'mariadb-client' ] # Определяем доступную версию PHP из стандартных репозиториев print("� Определение доступной версии PHP...") self.run_command('sudo apt update') # Получаем версию PHP по умолчанию для дистрибутива php_version = self.get_default_php_version() if php_version: print(f"📦 Будет установлена версия PHP: {php_version}") # Добавляем пакеты PHP для найденной версии php_packages = [ f'php{php_version}', f'php{php_version}-fpm', f'php{php_version}-mysql', f'php{php_version}-cli', f'php{php_version}-curl', f'php{php_version}-gd', f'php{php_version}-mbstring', f'php{php_version}-xml', f'php{php_version}-zip', f'php{php_version}-intl', f'php{php_version}-bcmath' ] else: print("📦 Попробуем установить стандартный пакет PHP") php_version = "" # Будем использовать стандартный пакет php_packages = [ 'php', 'php-fpm', 'php-mysql', 'php-cli', 'php-curl', 'php-gd', 'php-mbstring', 'php-xml', 'php-zip', 'php-intl', 'php-bcmath' ] packages.extend(php_packages) # Проверяем, что уже установлено services_to_check = { 'apache2': ['apache2'], 'nginx': ['nginx'], 'php': [f'php{php_version}' if php_version else 'php'], 'mariadb': ['mariadb', 'mysql'] } installed_services = [] for service, commands in services_to_check.items(): for cmd in commands: if self.check_installed(cmd): installed_services.append(service) print(f"✅ {service} уже установлен") break # Обновляем списки пакетов (уже выполнено выше) print("📦 Обновление списка пакетов...") # Устанавливаем пакеты print("⬬ Установка пакетов...") install_cmd = 'sudo apt install -y' # Фильтруем уже установленные пакеты packages_to_install = packages[:] if 'apache2' in installed_services: packages_to_install = [p for p in packages_to_install if p != 'apache2'] if 'nginx' in installed_services: packages_to_install = [p for p in packages_to_install if p != 'nginx'] if 'php' in installed_services: if php_version: packages_to_install = [p for p in packages_to_install if not p.startswith(f'php{php_version}')] else: packages_to_install = [p for p in packages_to_install if not p.startswith('php')] if 'mariadb' in installed_services: packages_to_install = [p for p in packages_to_install if not p.startswith('mariadb')] if packages_to_install: cmd = f"{install_cmd} {' '.join(packages_to_install)}" if not self.run_command(cmd): print("❌ Ошибка при установке пакетов") return False print("✅ Пакеты установлены") else: print("✅ Все пакеты уже установлены") # Запускаем и включаем сервисы services = ['apache2', 'nginx', 'mariadb'] print("🔧 Настройка сервисов...") for service in services: print(f" Настройка {service}...") self.run_command(f'sudo systemctl enable {service}', check=False) self.run_command(f'sudo systemctl start {service}', check=False) # После установки определяем реальную версию PHP if not php_version: print("🔍 Определение установленной версии PHP...") php_version = self.get_installed_php_version() if php_version: print(f"✅ Установлена версия PHP: {php_version}") else: print("❌ Не удалось определить установленную версию PHP!") return False # Настройка PHP-FPM для работы от имени пользователя print(f"🐘 Настройка PHP {php_version} FPM для пользователя {self.current_user}...") self.run_command(f'sudo systemctl enable php{php_version}-fpm', check=False) # Создаем пул PHP-FPM для пользователя pool_config = f"""[{self.current_user}] user = {self.current_user} group = {self.user_group} listen = /run/php/php{php_version}-fpm-{self.current_user}.sock listen.owner = {self.current_user} listen.group = {self.user_group} listen.mode = 0660 pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 """ with open('/tmp/user_pool.conf', 'w') as f: f.write(pool_config) self.run_command(f'sudo mv /tmp/user_pool.conf /etc/php/{php_version}/fpm/pool.d/{self.current_user}.conf') self.run_command(f'sudo systemctl start php{php_version}-fpm') self.run_command(f'sudo systemctl reload php{php_version}-fpm') print(f"✅ PHP {php_version} FPM настроен для пользователя {self.current_user}") # Настройка Apache для работы от имени пользователя на порту 8080 print(f"🔧 Настройка Apache для пользователя {self.current_user} (порт 8080)...") # Изменяем порт Apache на 8080 apache_ports_config = """# Ports configuration for Apache behind Nginx Listen 8080 <IfModule ssl_module> Listen 8443 </IfModule> <IfModule mod_gnutls.c> Listen 8443 </IfModule> """ with open('/tmp/apache_ports.conf', 'w') as f: f.write(apache_ports_config) self.run_command('sudo mv /tmp/apache_ports.conf /etc/apache2/ports.conf') # Создаем конфигурацию для пользователя apache_user_config = f""" # Настройка для пользователя {self.current_user} <IfModule mpm_prefork_module> User {self.current_user} Group {self.user_group} </IfModule> <IfModule mpm_worker_module> User {self.current_user} Group {self.user_group} </IfModule> <IfModule mpm_event_module> User {self.current_user} Group {self.user_group} </IfModule> """ with open('/tmp/apache_user.conf', 'w') as f: f.write(apache_user_config) self.run_command(f'sudo mv /tmp/apache_user.conf /etc/apache2/conf-available/{self.current_user}.conf') self.run_command(f'sudo a2enconf {self.current_user}') # Отключаем дефолтный сайт Apache self.run_command('sudo a2dissite 000-default', check=False) # Включаем необходимые модули Apache self.run_command('sudo a2enmod rewrite', check=False) self.run_command('sudo a2enmod ssl', check=False) self.run_command('sudo a2enmod proxy_fcgi', check=False) self.run_command('sudo a2enmod setenvif', check=False) self.run_command(f'sudo a2enconf php{php_version}-fpm', check=False) # Настройка Nginx как фронтенд прокси на порту 80 print(f"🔧 Настройка Nginx как фронтенд прокси для пользователя {self.current_user}...") # Создаем основную конфигурацию nginx nginx_user_config = f"""user {self.current_user}; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events {{ worker_connections 768; use epoll; multi_accept on; }} http {{ sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 64M; include /etc/nginx/mime.types; default_type application/octet-stream; # Логирование log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; # Gzip компрессия gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; # Настройки прокси proxy_buffering on; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; proxy_temp_file_write_size 256k; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; # Отключаем дефолтный сервер server {{ listen 80 default_server; server_name _; return 444; }} include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }}""" with open('/tmp/nginx.conf', 'w') as f: f.write(nginx_user_config) self.run_command('sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup', check=False) self.run_command('sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf') # Удаляем дефолтный сайт Nginx self.run_command('sudo rm -f /etc/nginx/sites-enabled/default', check=False) # Настраиваем права доступа к логам self.run_command(f'sudo chown -R {self.current_user}:{self.user_group} /var/log/nginx/', check=False) self.run_command(f'sudo chown -R {self.current_user}:{self.user_group} /var/log/apache2/', check=False) # Перезагружаем сервисы self.run_command('sudo systemctl reload apache2', check=False) if self.run_command('sudo nginx -t', check=False): self.run_command('sudo systemctl reload nginx', check=False) else: print("❌ Ошибка в конфигурации Nginx, восстанавливаем бэкап") self.run_command('sudo mv /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf', check=False) self.run_command('sudo systemctl reload nginx', check=False) print("✅ Установка и настройка завершены!") print(f"\n📋 Установленные компоненты для пользователя {self.current_user}:") print(" • Nginx (порт 80) - фронтенд прокси + статические файлы") print(" • Apache (порт 8080) - бэкенд + PHP обработка") print(f" • PHP {php_version} - язык программирования") print(" • MariaDB - база данных") print(f"\n👤 Веб-серверы настроены для работы от имени пользователя: {self.current_user}") print("\n🔧 Архитектура:") print(" Nginx (фронтенд) ──> Apache (бэкенд) ──> PHP-FPM") print(" ↓") print(" Статические файлы") print("\n🔐 Настройка MariaDB...") self.setup_mariadb() print("🌐 Теперь вы можете добавлять сайты!") return True def add_site(self, domain, directory, create_db=True): """Добавляет новый сайт в систему с Nginx + Apache""" print(f"🌐 Добавление сайта: {domain} -> {directory}") print("🔄 Конфигурация: Nginx (фронтенд) + Apache (бэкенд)") # Создаем директорию сайта site_dir = Path(directory) site_dir.mkdir(parents=True, exist_ok=True) # Создание базы данных db_name = None if create_db: # Генерируем имя БД на основе домена db_name_base = domain.replace('.', '_').replace('-', '_') db_name_base = ''.join(c for c in db_name_base if c.isalnum() or c == '_') db_name = f"{self.current_user}_{db_name_base}"[:64] created_db = self.create_database(db_name) if created_db: db_name = created_db self.create_database_config_file(domain, db_name) else: print("⚠️ Продолжаем без базы данных") db_name = None # Создаем простой index.php если его нет index_file = site_dir / 'index.php' if not index_file.exists(): db_info = "" if db_name: db_info = f""" echo "<h3>🗄️ База данных:</h3>"; echo "<p><strong>Имя БД:</strong> {db_name}</p>"; echo "<p><strong>Файл настроек:</strong> .env</p>"; try {{ $pdo = new PDO("mysql:host=localhost;dbname={db_name};charset=utf8mb4", getenv('DB_USER') ?: '{self.sites_config.get('database', {}).get('user', '')}', getenv('DB_PASSWORD') ?: '{self.sites_config.get('database', {}).get('password', '')}'); echo "<p><strong>Соединение с БД:</strong> ✅ Успешно</p>"; }} catch(PDOException $e) {{ echo "<p><strong>Соединение с БД:</strong> ❌ Ошибка: " . $e->getMessage() . "</p>"; }}""" with open(index_file, 'w') as f: f.write(f"""<?php echo "<h1>🌐 Добро пожаловать на {domain}</h1>"; echo "<h2>🚀 Локальный сервер разработки</h2>"; echo "<p>✅ Сайт успешно настроен!</p>"; echo "<hr>"; echo "<h3>📋 Информация о системе:</h3>"; echo "<p><strong>PHP версия:</strong> " . phpversion() . "</p>"; echo "<p><strong>Пользователь:</strong> {self.current_user}</p>"; echo "<p><strong>Директория:</strong> " . __DIR__ . "</p>"; echo "<p><strong>Время:</strong> " . date('Y-m-d H:i:s') . "</p>"; echo "<h3>🔧 Архитектура сервера:</h3>"; echo "<p><strong>Фронтенд:</strong> Nginx (порт 80) - статические файлы</p>"; echo "<p><strong>Бэкенд:</strong> Apache (порт 8080) - PHP обработка</p>"; echo "<p><strong>X-Served-By:</strong> " . ($_SERVER['HTTP_X_SERVED_BY'] ?? 'Unknown') . "</p>";{db_info} echo "<hr>"; echo "<h3>🐘 Подробная информация о PHP:</h3>"; echo "<details><summary>Нажмите для просмотра phpinfo()</summary>"; phpinfo(); echo "</details>"; ?>""") # Настройка прав доступа для текущего пользователя self.run_command(f'chown -R {self.current_user}:{self.user_group} {directory}') self.run_command(f'chmod -R 755 {directory}') # Создаем конфигурации для обоих серверов nginx_success = self.configure_nginx(domain, directory) apache_success = self.configure_apache(domain, directory) if not nginx_success or not apache_success: print("❌ Ошибка при создании конфигураций") return False # Сохраняем конфигурацию сайта site_config = { 'domain': domain, 'directory': directory, 'webserver': 'nginx+apache', # Указываем, что используем оба 'nginx_port': 80, 'apache_port': 8080, 'database': db_name, 'created': subprocess.check_output('date', text=True).strip() } self.sites_config['sites'].append(site_config) self.save_config() print(f"✅ Сайт {domain} успешно добавлен!") print("🔧 Конфигурация:") print(" • Nginx (порт 80) - фронтенд прокси + статические файлы") print(" • Apache (порт 8080) - бэкенд + PHP обработка") if db_name: print(f" 🗄️ База данных {db_name} создана") return True def configure_nginx(self, domain, directory): """Создает конфигурацию Nginx как фронтенд прокси""" config_content = f"""server {{ listen 80; server_name {domain}; root {directory}; index index.php index.html index.htm; # Логи access_log /var/log/nginx/{domain}_access.log main; error_log /var/log/nginx/{domain}_error.log; # Статические файлы обрабатывает Nginx location ~* \\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|tar|rar|doc|docx|xls|xlsx)$ {{ root {directory}; expires 1y; add_header Cache-Control "public, immutable"; add_header X-Served-By "Nginx"; try_files $uri =404; }} # Специальные директории (если есть) location /assets/ {{ root {directory}; expires 1y; add_header Cache-Control "public"; }} location /uploads/ {{ root {directory}; expires 30d; add_header Cache-Control "public"; }} # Все остальные запросы проксируем в Apache location / {{ proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; # Буферизация proxy_buffering on; proxy_buffer_size 8k; proxy_buffers 8 8k; # Таймауты proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; # Добавляем заголовок для отладки add_header X-Served-By "Nginx->Apache"; }} # Запрет доступа к скрытым файлам location ~ /\\. {{ deny all; access_log off; log_not_found off; }} # Запрет доступа к конфигурационным файлам location ~* \\.(env|conf|config|ini|log)$ {{ deny all; access_log off; log_not_found off; }} # Оптимизация для роботов location = /robots.txt {{ allow all; log_not_found off; access_log off; }} location = /favicon.ico {{ log_not_found off; access_log off; expires 1y; add_header Cache-Control "public, immutable"; }} }}""" config_file = f'/etc/nginx/sites-available/{domain}' with open('/tmp/nginx_site.conf', 'w') as f: f.write(config_content) self.run_command(f'sudo mv /tmp/nginx_site.conf {config_file}') self.run_command(f'sudo ln -sf {config_file} /etc/nginx/sites-enabled/') # Проверяем конфигурацию и перезагружаем if self.run_command('sudo nginx -t', check=False): self.run_command('sudo systemctl reload nginx') print(f"✅ Nginx конфигурация (фронтенд) для {domain} создана") else: print(f"❌ Ошибка в конфигурации Nginx для {domain}") return False return True def configure_apache(self, domain, directory): """Создает конфигурацию Apache как бэкенд на порту 8080""" # Определяем установленную версию PHP php_version = self.get_installed_php_version() if not php_version: print("❌ Не удалось определить версию PHP для конфигурации Apache") return False config_content = f"""<VirtualHost *:8080> ServerName {domain} DocumentRoot {directory} <Directory {directory}> AllowOverride All Require all granted Options Indexes FollowSymLinks # Отключаем обработку статических файлов (их обрабатывает Nginx) <FilesMatch "\\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|tar|rar|doc|docx|xls|xlsx)$"> # Nginx уже обработал эти файлы, если запрос дошел сюда Header always set X-Served-By "Apache-Backend" </FilesMatch> </Directory> # PHP-FPM configuration <FilesMatch \\.php$> SetHandler "proxy:unix:/run/php/php{php_version}-fpm-{self.current_user}.sock|fcgi://localhost" </FilesMatch> # Добавляем заголовки для отладки Header always set X-Backend "Apache" Header always set X-Backend-Port "8080" # Логи ErrorLog /var/log/apache2/{domain}_error.log CustomLog /var/log/apache2/{domain}_access.log combined # Настройки для работы за прокси SetEnvIf X-Forwarded-Proto https HTTPS=on SetEnvIf X-Forwarded-For ^(.*)$ HTTP_X_FORWARDED_FOR=$1 </VirtualHost>""" config_file = f'/etc/apache2/sites-available/{domain}.conf' with open('/tmp/apache_site.conf', 'w') as f: f.write(config_content) self.run_command(f'sudo mv /tmp/apache_site.conf {config_file}') self.run_command(f'sudo a2ensite {domain}') # Включаем модуль headers для отладочных заголовков self.run_command('sudo a2enmod headers', check=False) # Проверяем конфигурацию и перезагружаем if self.run_command('sudo apache2ctl configtest', check=False): self.run_command('sudo systemctl reload apache2') print(f"✅ Apache конфигурация (бэкенд) для {domain} создана") else: print(f"❌ Ошибка в конфигурации Apache для {domain}") return False return True # Проверяем конфигурацию и перезагружаем if self.run_command('sudo apache2ctl configtest', check=False): self.run_command('sudo systemctl reload apache2') print(f"✅ Apache конфигурация для {domain} создана") else: print(f"❌ Ошибка в конфигурации Apache для {domain}") return False return True def list_sites(self): """Показывает список всех сайтов""" print("\n📋 Список сайтов:") print("-" * 70) if not self.sites_config['sites']: print("Сайты не найдены") return for i, site in enumerate(self.sites_config['sites'], 1): print(f"{i}. {site['domain']}") print(f" 📁 Директория: {site['directory']}") # Информация о веб-серверах if site.get('webserver') == 'nginx+apache': print(f" 🌐 Архитектура: Nginx (фронтенд :80) + Apache (бэкенд :8080)") else: # Поддержка старых конфигураций print(f" 🌐 Веб-сервер: {site['webserver']} (устаревшая конфигурация)") # Информация о базе данных if site.get('database'): print(f" 🗄️ База данных: {site['database']}") else: print(f" 🗄️ База данных: не создана") print(f" 📅 Создан: {site['created']}") print() # Показываем информацию о пользователе БД if 'database' in self.sites_config: print("=" * 70) print("🔐 Настройки базы данных:") print(f" 👤 Пользователь БД: {self.sites_config['database']['user']}") print(f" 🏠 Хост: {self.sites_config['database']['host']}") print(f" 🔑 Пароль: сохранен в конфигурации") # Показываем список всех баз данных databases = self.list_databases() if databases: print(f" 📊 Всего БД: {len(databases)}") for db in databases[:5]: # Показываем первые 5 print(f" • {db}") if len(databases) > 5: print(f" ... и еще {len(databases) - 5}") else: print("⚠️ Пользователь БД не настроен. Запустите установку.") def list_databases_detailed(self): """Показывает подробный список баз данных""" print("\n🗄️ Базы данных:") print("-" * 50) if 'database' not in self.sites_config: print("❌ Пользователь БД не настроен") return databases = self.list_databases() if not databases: print("Базы данных не найдены") return for i, db in enumerate(databases, 1): # Проверяем, к какому сайту относится БД site_domain = "не привязана" for site in self.sites_config['sites']: if site.get('database') == db: site_domain = site['domain'] break print(f"{i}. {db}") print(f" 🌐 Сайт: {site_domain}") print() def remove_site(self, domain, delete_db=True): """Удаляет сайт из системы (Nginx + Apache)""" print(f"🗑️ Удаление сайта: {domain}") # Находим сайт в конфигурации site_to_remove = None for site in self.sites_config['sites']: if site['domain'] == domain: site_to_remove = site break if not site_to_remove: print(f"❌ Сайт {domain} не найден") return False # Удаляем базу данных если она есть if delete_db and site_to_remove.get('database'): db_name = site_to_remove['database'] print(f"🗄️ Удаление базы данных: {db_name}") self.delete_database(db_name) # Удаляем конфигурацию Nginx print("🔧 Удаление конфигурации Nginx...") self.run_command(f'sudo rm -f /etc/nginx/sites-available/{domain}', check=False) self.run_command(f'sudo rm -f /etc/nginx/sites-enabled/{domain}', check=False) # Удаляем конфигурацию Apache print("🔧 Удаление конфигурации Apache...") self.run_command(f'sudo a2dissite {domain}', check=False) self.run_command(f'sudo rm -f /etc/apache2/sites-available/{domain}.conf', check=False) # Перезагружаем оба сервера nginx_reload = self.run_command('sudo systemctl reload nginx', check=False) apache_reload = self.run_command('sudo systemctl reload apache2', check=False) if nginx_reload: print("✅ Nginx перезагружен") else: print("⚠️ Проблема с перезагрузкой Nginx") if apache_reload: print("✅ Apache перезагружен") else: print("⚠️ Проблема с перезагрузкой Apache") # Удаляем из конфигурации self.sites_config['sites'].remove(site_to_remove) self.save_config() print(f"✅ Сайт {domain} удален!") if site_to_remove.get('database') and delete_db: print(f"✅ База данных {site_to_remove['database']} удалена!") return True def generate_password(self, length=16): """Генерирует случайный пароль""" alphabet = string.ascii_letters + string.digits + "!@#$%^&*" return ''.join(secrets.choice(alphabet) for _ in range(length)) def setup_mariadb(self): """Настраивает MariaDB для работы с пользователем""" print("🔐 Настройка MariaDB...") # Проверяем, запущен ли MariaDB if not self.run_command('sudo systemctl is-active mariadb', check=False): print("❌ MariaDB не запущен!") return False # Создаем пользователя MariaDB для текущего пользователя системы db_user = f"{self.current_user}_db" db_password = self.generate_password() print(f"👤 Создание пользователя БД: {db_user}") # SQL команды для создания пользователя sql_commands = f""" CREATE USER IF NOT EXISTS '{db_user}'@'localhost' IDENTIFIED BY '{db_password}'; GRANT CREATE, DROP, SELECT, INSERT, UPDATE, DELETE, ALTER, INDEX ON *.* TO '{db_user}'@'localhost'; FLUSH PRIVILEGES; """ # Выполняем SQL команды sql_file = '/tmp/setup_db_user.sql' with open(sql_file, 'w') as f: f.write(sql_commands) if self.run_command(f'sudo mysql < {sql_file}', check=False): # Сохраняем данные пользователя БД в конфигурацию if 'database' not in self.sites_config: self.sites_config['database'] = {} self.sites_config['database']['user'] = db_user self.sites_config['database']['password'] = db_password self.sites_config['database']['host'] = 'localhost' self.save_config() print(f"✅ Пользователь БД {db_user} создан") print(f"🔑 Пароль сохранен в конфигурации") # Удаляем временный файл os.remove(sql_file) return True else: print("❌ Ошибка при создании пользователя БД") if os.path.exists(sql_file): os.remove(sql_file) return False def create_database(self, db_name): """Создает базу данных для сайта""" if 'database' not in self.sites_config: print("❌ Пользователь БД не настроен. Запустите установку сначала.") return False db_user = self.sites_config['database']['user'] db_password = self.sites_config['database']['password'] print(f"🗄️ Создание базы данных: {db_name}") # Очищаем имя БД от недопустимых символов clean_db_name = ''.join(c for c in db_name if c.isalnum() or c == '_') clean_db_name = clean_db_name[:64] # Ограничение MySQL на длину имени БД sql_commands = f""" CREATE DATABASE IF NOT EXISTS `{clean_db_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON `{clean_db_name}`.* TO '{db_user}'@'localhost'; FLUSH PRIVILEGES; """ sql_file = '/tmp/create_database.sql' with open(sql_file, 'w') as f: f.write(sql_commands) if self.run_command(f'sudo mysql < {sql_file}', check=False): print(f"✅ База данных {clean_db_name} создана") os.remove(sql_file) return clean_db_name else: print(f"❌ Ошибка при создании базы данных {clean_db_name}") if os.path.exists(sql_file): os.remove(sql_file) return False def delete_database(self, db_name): """Удаляет базу данных""" if not db_name: return True print(f"🗑️ Удаление базы данных: {db_name}") sql_commands = f"DROP DATABASE IF EXISTS `{db_name}`;" sql_file = '/tmp/delete_database.sql' with open(sql_file, 'w') as f: f.write(sql_commands) if self.run_command(f'sudo mysql < {sql_file}', check=False): print(f"✅ База данных {db_name} удалена") os.remove(sql_file) return True else: print(f"❌ Ошибка при удалении базы данных {db_name}") if os.path.exists(sql_file): os.remove(sql_file) return False def list_databases(self): """Показывает список баз данных пользователя""" if 'database' not in self.sites_config: print("❌ Пользователь БД не настроен") return [] db_user = self.sites_config['database']['user'] # Получаем список баз данных result = subprocess.run( f"sudo mysql -e \"SHOW DATABASES;\" | grep -v 'Database\\|information_schema\\|performance_schema\\|mysql\\|sys'", shell=True, capture_output=True, text=True ) if result.returncode == 0: databases = [db.strip() for db in result.stdout.split('\n') if db.strip()] return databases return [] def create_database_config_file(self, domain, db_name): """Создает файл конфигурации БД для сайта""" if 'database' not in self.sites_config: return False db_user = self.sites_config['database']['user'] db_password = self.sites_config['database']['password'] # Находим директорию сайта site_directory = None for site in self.sites_config['sites']: if site['domain'] == domain: site_directory = site['directory'] break if not site_directory: return False # Создаем файл .env с настройками БД env_content = f"""# Database Configuration for {domain} DB_HOST=localhost DB_NAME={db_name} DB_USER={db_user} DB_PASSWORD={db_password} DB_CHARSET=utf8mb4 DB_COLLATION=utf8mb4_unicode_ci # Example usage in PHP: # $pdo = new PDO("mysql:host=localhost;dbname={db_name};charset=utf8mb4", "{db_user}", "{db_password}"); """ env_file = Path(site_directory) / '.env' try: with open(env_file, 'w') as f: f.write(env_content) # Устанавливаем права доступа self.run_command(f'chown {self.current_user}:{self.user_group} {env_file}') self.run_command(f'chmod 600 {env_file}') # Только владелец может читать print(f"📄 Создан файл конфигурации БД: {env_file}") return True except Exception as e: print(f"❌ Ошибка при создании файла конфигурации: {e}") return False def main(): parser = argparse.ArgumentParser(description='Web Server Installer and Site Manager') parser.add_argument('action', choices=['install', 'add', 'list', 'remove', 'databases'], help='Действие для выполнения') parser.add_argument('--domain', help='Доменное имя сайта') parser.add_argument('--directory', help='Директория сайта') parser.add_argument('--no-database', action='store_true', help='Не создавать базу данных для сайта') parser.add_argument('--keep-database', action='store_true', help='Не удалять базу данных при удалении сайта') args = parser.parse_args() installer = WebServerInstaller() if args.action == 'install': installer.install_packages() elif args.action == 'add': if not args.domain or not args.directory: print("❌ Необходимо указать --domain и --directory") sys.exit(1) create_db = not args.no_database installer.add_site(args.domain, args.directory, create_db) elif args.action == 'list': installer.list_sites() elif args.action == 'databases': installer.list_databases_detailed() elif args.action == 'remove': if not args.domain: print("❌ Необходимо указать --domain") sys.exit(1) delete_db = not args.keep_database installer.remove_site(args.domain, delete_db) if __name__ == '__main__': main()