From 3acf2c35fc98e840d60979f81d6359157749b4f9 Mon Sep 17 00:00:00 2001 From: Mikan <72257910+Mikan-DS@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:52:02 +0300 Subject: [PATCH] initial --- .gitignore | 1 + docker-compose.yml | 55 +++++++++++++++++ scripts/cert_renew_restart.py | 113 ++++++++++++++++++++++++++++++++++ scripts/common.py | 112 +++++++++++++++++++++++++++++++++ scripts/restart_services.py | 22 +++++++ vhost.d/_local | 2 + 6 files changed, 305 insertions(+) create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 scripts/cert_renew_restart.py create mode 100644 scripts/common.py create mode 100644 scripts/restart_services.py create mode 100644 vhost.d/_local diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df91287 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +certs/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..669e4cd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +services: + dnsmasq: + image: jpillora/dnsmasq + container_name: dnsmasq + restart: always + ports: + - "53:53/udp" + - "53:53/tcp" + volumes: + - ./dnsmasq.conf:/etc/dnsmasq.conf:ro + cap_add: + - NET_ADMIN + networks: + - devnet + + nginx-proxy: + image: nginxproxy/nginx-proxy:alpine + container_name: nginx-proxy + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs:ro + - ./vhost.d:/etc/nginx/vhost.d:ro + - ./html:/usr/share/nginx/html:rw + networks: + - devnet + labels: + - "com.github.nginx-proxy.nginx" + + nginx-proxy-companion: + image: nginxproxy/acme-companion + container_name: nginx-proxy-companion + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./certs:/etc/nginx/certs:rw + - ./acme:/etc/acme.sh + - ./vhost.d:/etc/nginx/vhost.d + - ./html:/usr/share/nginx/html + networks: + - devnet + environment: + - NGINX_PROXY_CONTAINER=nginx-proxy + +networks: + devnet: + external: true + +volumes: + acme: + vhost.d: + html: diff --git a/scripts/cert_renew_restart.py b/scripts/cert_renew_restart.py new file mode 100644 index 0000000..0d73374 --- /dev/null +++ b/scripts/cert_renew_restart.py @@ -0,0 +1,113 @@ +# cert_renew_restart.py + + + +def generate_mkcert_command(): + """Генерирует команду для mkcert с указанием всех доменов""" + services = read_services_config() + domains = [f"{service['name']}.local" for service in services['services']] + domains.append("*.local") + + # Создаем PowerShell команду + domains_str = " ".join([f'"{domain}"' for domain in domains]) + powershell_cmd = f'mkcert {domains_str}' + return powershell_cmd + +def copy_and_rename_certificates(): + """Копирует и переименовывает сертификаты из Windows в WSL""" + # Определяем пути + windows_certs_path = Path("C:/store/certs") + wsl_certs_path = Path.home() / "projects" / "docker" / "network" / "certs" + + # Создаем директорию для сертификатов в WSL + wsl_certs_path.mkdir(parents=True, exist_ok=True) + + # Находим сгенерированные mkcert файлы + cert_files = list(windows_certs_path.glob("_wildcard.local+*.pem")) + if not cert_files: + print("Сертификаты не найдены в C:/store/certs") + return False + + # Предполагаем, что mkcert генерирует файлы в формате _wildcard.domain+N.pem + cert_file = cert_files[0] + key_file = cert_files[0].with_name(cert_file.name.replace(".pem", "-key.pem")) + + if not key_file.exists(): + print(f"Файл ключа не найден: {key_file}") + return False + + # Копируем и переименовываем + target_cert = wsl_certs_path / "wildcard.local.pem" + target_key = wsl_certs_path / "wildcard.local.key" + + shutil.copy2(cert_file, target_cert) + shutil.copy2(key_file, target_key) + + # Создаем копии для default + default_cert = wsl_certs_path / "default.crt" + default_key = wsl_certs_path / "default.key" + + shutil.copy2(target_cert, default_cert) + shutil.copy2(target_key, default_key) + + print(f"Сертификаты скопированы в {wsl_certs_path}") + return True + +def run_docker_command(cmd): + """Выполняет команду Docker и возвращает результат""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if result.returncode != 0: + print(f"Ошибка выполнения команды: {cmd}") + print(f"Ошибка: {result.stderr}") + return False + return True + except Exception as e: + print(f"Ошибка выполнения команды: {e}") + return False + +def main(): + print("Обновление сертификатов и перезапуск сервисов...") + + # Получаем имя директории проекта (предполагаем, что это "network") + project_name = "network" + + # Генерируем конфигурационный файл dnsmasq + generate_dnsmasq_config() + + # Выводим команду для генерации сертификатов + mkcert_cmd = generate_mkcert_command() + print("\nДля генерации сертификатов выполните в PowerShell (от администратора):") + print(f"cd C:\\store\\certs") + print(mkcert_cmd) + print() + + # Ждем подтверждения пользователя + input("Нажмите Enter после генерации сертификатов в PowerShell...") + + # Копируем сертификаты + if not copy_and_rename_certificates(): + print("Не удалось скопировать сертификаты") + sys.exit(1) + + # Переходим в директорию проекта + project_path = Path.home() / "projects" / "docker" / project_name + if not project_path.exists(): + print(f"Директория проекта не найдена: {project_path}") + sys.exit(1) + + os.chdir(project_path) + + # Перезапускаем nginx-proxy для загрузки новых сертификатов + print("Перезапуск nginx-proxy...") + if not run_docker_command("docker-compose restart nginx-proxy"): + print("Не удалось перезапустить nginx-proxy") + # Если restart не сработал, пробуем поднять контейнеры + if not run_docker_command("docker-compose up -d"): + print("Не удалось поднять контейнеры") + sys.exit(1) + + print("Сертификаты обновлены и сервисы перезапущены!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/common.py b/scripts/common.py new file mode 100644 index 0000000..ef1fa2e --- /dev/null +++ b/scripts/common.py @@ -0,0 +1,112 @@ +# common.py +import subprocess +import sys +import os +import platform +import json +from pathlib import Path + +def get_host_ip(): + """Получает IP-адрес хоста в локальной сети""" + try: + # Для WSL2 получаем IP хоста (Windows) + if 'microsoft' in platform.uname().release.lower(): + # В WSL2 получаем IP хоста из файла resolv.conf + with open('/etc/resolv.conf', 'r') as f: + for line in f: + if line.startswith('nameserver'): + return line.split()[1] + # Для других систем используем подключение к внешнему серверу + else: + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception as e: + print(f"Не удалось получить IP-адрес хоста: {e}") + # Возвращаем IP по умолчанию, если не удалось получить + return "192.168.31.60" + +def read_services_config(): + """Читает конфигурацию сервисов из файла""" + config_path = Path.home() / "projects" / "docker" / "network" / "services.json" + default_config = { + "services": [ + {"name": "gitea", "port": 3000}, + {"name": "youtrack", "port": 8080}, + {"name": "n8n", "port": 5678} + ] + } + + if config_path.exists(): + with open(config_path, 'r', encoding='utf-8') as f: + return json.load(f) + else: + # Создаем файл конфигурации по умолчанию + config_path.parent.mkdir(parents=True, exist_ok=True) + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(default_config, f, indent=2, ensure_ascii=False) + return default_config + +def generate_dnsmasq_config(): + """Генерирует конфигурационный файл dnsmasq""" + host_ip = get_host_ip() + services = read_services_config() + + config_content = f"""# Локальные домены → твой ПК +""" + for service in services['services']: + config_content += f"address=/{service['name']}.local/{host_ip}\n" + + config_content += f"""address=/.local/{host_ip} + +# Кэш DNS +cache-size=500 + +# Лог (опционально) +log-queries +log-facility=/var/log/dnsmasq.log + +# Не перехватывать внешние запросы — делегируем +no-resolv +server=8.8.8.8 +server=1.1.1.1 +""" + + # Путь к конфигурационному файлу + config_path = Path.home() / "projects" / "docker" / "network" / "dnsmasq.conf" + config_path.parent.mkdir(parents=True, exist_ok=True) + + with open(config_path, 'w', encoding='utf-8') as f: + f.write(config_content.strip()) + + print(f"Конфигурационный файл dnsmasq обновлен: {config_path}") + +def run_docker_command(cmd): + """Выполняет команду Docker и возвращает результат""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if result.returncode != 0: + print(f"Ошибка выполнения команды: {cmd}") + print(f"Ошибка: {result.stderr}") + return False + return True + except Exception as e: + print(f"Ошибка выполнения команды: {e}") + return False + +def setup_project(): + """Настройка проекта: генерация конфигов и переход в директорию""" + # Генерируем конфигурационный файл dnsmasq + generate_dnsmasq_config() + + # Переходим в директорию проекта + project_path = Path.home() / "projects" / "docker" / "network" + if not project_path.exists(): + print(f"Директория проекта не найдена: {project_path}") + sys.exit(1) + + os.chdir(project_path) + return project_path \ No newline at end of file diff --git a/scripts/restart_services.py b/scripts/restart_services.py new file mode 100644 index 0000000..076b849 --- /dev/null +++ b/scripts/restart_services.py @@ -0,0 +1,22 @@ +# restart_services.py +from common import * + +def main(): + print("Перезапуск сервисов...") + + # Настройка проекта + setup_project() + + # Перезапускаем контейнеры + print("Перезапуск контейнеров...") + if not run_docker_command("docker-compose restart"): + print("Не удалось перезапустить контейнеры") + # Если restart не сработал, пробуем поднять контейнеры + if not run_docker_command("docker-compose up -d"): + print("Не удалось поднять контейнеры") + sys.exit(1) + + print("Сервисы перезапущены!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/vhost.d/_local b/vhost.d/_local new file mode 100644 index 0000000..29f2e42 --- /dev/null +++ b/vhost.d/_local @@ -0,0 +1,2 @@ +ssl_certificate /etc/nginx/certs/wildcard.local.pem; +ssl_certificate_key /etc/nginx/certs/wildcard.local.key;