This commit is contained in:
Mikan
2026-02-13 01:47:50 +03:00
parent 3831545d38
commit 60204db3d2
15 changed files with 420 additions and 323 deletions

41
scripts/00-init.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Копирую из .env.example..."
cp "$PROJECT_ROOT/.env.example" "$PROJECT_ROOT/.env"
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
# Создаём каталоги
mkdir -p \
"$PROJECT_ROOT/dnsmasq" \
"$PROJECT_ROOT/nginx/{certs,vhost.d,html,conf.d}" \
"$PROJECT_ROOT/acme" \
"$PROJECT_ROOT/ca" \
"$PROJECT_ROOT/services"
# Создаём общую Docker-сеть с фиксированными настройками
echo "Проверка сети devnet..."
if ! docker network inspect devnet &>/dev/null; then
echo "Создаём сеть devnet..."
docker network create \
--driver=bridge \
--subnet="${SUBNET}" \
--gateway="${GATEWAY}" \
devnet
else
echo "Сеть devnet уже существует."
fi
"$SCRIPT_DIR/02-apply-config.sh"
echo "Проект инициализирован."
echo "Домен: .$LOCAL_DOMAIN"
if [ -n "$PUBLIC_DOMAIN" ]; then
echo "Публичный домен: $PUBLIC_DOMAIN"
fi

View File

@@ -0,0 +1,49 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Запустите сначала ./scripts/00-init.sh"
exit 1
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
CERTS_DIR="$PROJECT_ROOT/nginx/certs"
CA_DIR="$PROJECT_ROOT/ca"
if ! command -v mkcert &> /dev/null; then
echo "mkcert не установлен."
echo "Linux: sudo apt install mkcert"
echo "macOS: brew install mkcert nss"
exit 1
fi
mkdir -p "$CERTS_DIR" "$CA_DIR"
DOMAINS=(
"*.$LOCAL_DOMAIN"
"*.$LOCAL_DOMAIN.local"
"$LOCAL_DOMAIN"
"localhost"
"127.0.0.1"
"::1"
)
if [ -n "$PUBLIC_DOMAIN" ]; then
DOMAINS+=("*.$PUBLIC_DOMAIN" "$PUBLIC_DOMAIN")
fi
mkcert -install
mkcert \
-cert-file "$CERTS_DIR/default.crt" \
-key-file "$CERTS_DIR/default.key" \
"${DOMAINS[@]}"
cp "$(mkcert -CAROOT)/rootCA.pem" "$CA_DIR/rootCA.pem"
echo "Сертификаты созданы в $CERTS_DIR"
echo "Установите корневой сертификат на устройствах:"
echo " $CA_DIR/rootCA.pem"

View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Запустите сначала ./scripts/00-init.sh"
exit 1
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
cat > "$PROJECT_ROOT/dnsmasq/dnsmasq.conf" <<EOF
address=/.$LOCAL_DOMAIN/$NGINX_IP
address=/.$LOCAL_DOMAIN.local/$NGINX_IP
local=/.$LOCAL_DOMAIN/
local=/.$LOCAL_DOMAIN.local/
no-resolv
server=1.1.1.1
server=8.8.8.8
log-queries
log-facility=/dev/stdout
EOF
mkdir -p "$PROJECT_ROOT/nginx/html"
cat > "$PROJECT_ROOT/nginx/html/index.html" <<EOF
<!DOCTYPE html>
<html>
<head><title>Добро пожаловать!</title></head>
<body>
<h1>Сервисы .$LOCAL_DOMAIN</h1>
<p>Добавьте свои сервисы в папку <code>services/</code></p>
</body>
</html>
EOF
echo "Конфигурация применена для .$LOCAL_DOMAIN"

65
scripts/add-service.sh Normal file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
set -e
if [ $# -ne 2 ]; then
echo "Использование: $0 <название> <порт>"
echo "Пример: $0 gitea 3000"
exit 1
fi
NAME=$1
PORT=$2
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Запустите сначала ./scripts/00-init.sh"
exit 1
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
SERVICE_DIR="$PROJECT_ROOT/services/$NAME"
if [ -d "$SERVICE_DIR" ]; then
echo "Сервис '$NAME' уже существует!"
exit 1
fi
mkdir -p "$SERVICE_DIR/data"
cat > "$SERVICE_DIR/docker-compose.yml" <<EOF
version: '3.8'
services:
$NAME:
image: nginx:alpine
container_name: $NAME
restart: unless-stopped
environment:
- VIRTUAL_HOST=$NAME.$LOCAL_DOMAIN,$NAME.$LOCAL_DOMAIN.local
$(if [ -n "$PUBLIC_DOMAIN" ]; then
echo " - LETSENCRYPT_HOST=$NAME.$PUBLIC_DOMAIN"
echo " - LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL"
else
echo " # LETSENCRYPT_HOST="
echo " # LETSENCRYPT_EMAIL="
fi)
- VIRTUAL_PORT=$PORT
volumes:
- .//data
networks:
- devnet
depends_on:
- nginx-proxy
networks:
devnet:
external: true
EOF
echo "Сервис '$NAME' создан в $SERVICE_DIR"
echo "Запустите:"
echo " cd $PROJECT_ROOT"
echo " docker compose -f docker-compose.yml -f services/$NAME/docker-compose.yml up -d"

View File

@@ -1,125 +0,0 @@
# cert_renew_restart.py
import subprocess
import shutil
from pathlib import Path
from common import *
def generate_mkcert_command():
"""Генерирует команду для mkcert с указанием всех доменов"""
project_path = get_project_root()
services = read_services_config(project_path)
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(project_path):
"""Копирует и переименовывает сертификаты из Windows в WSL в директорию проекта."""
# Определяем пути
windows_certs_path = Path("C:/store/certs")
# Путь к директории certs внутри проекта local-dns
wsl_certs_path = project_path / "certs"
# Создаем директорию для сертификатов в WSL в корне проекта
wsl_certs_path.mkdir(parents=True, exist_ok=True)
# Находим сгенерированные mkcert файлы (обычно именуются как _wildcard.local+*.pem)
# Предполагаем, что первый найденный файл - это сертификат
cert_files = list(windows_certs_path.glob("_wildcard.local*.pem"))
if not cert_files:
print(f"Сертификаты не найдены в {windows_certs_path} по шаблону _wildcard.local*.pem")
return False
# Сортируем, чтобы упростить выбор (хотя mkcert обычно создает пару)
cert_files.sort()
cert_file = cert_files[0]
# Пытаемся найти соответствующий ключ (обычно имя файла сертификата с -key.pem)
# Например, если сертификат _wildcard.local+2.pem, ключ будет _wildcard.local+2-key.pem
expected_key_name = cert_file.with_name(cert_file.name.replace('.pem', '-key.pem'))
if expected_key_name.exists():
key_file = expected_key_name
else:
print(f"Файл ключа не найден по ожидаемому пути: {expected_key_name}")
# Альтернатива: ищем файл с -key.pem в имени среди найденных
key_candidates = [f for f in cert_files if '-key.pem' in f.name]
if key_candidates:
key_file = key_candidates[0] # Берем первый найденный ключ
print(f"Найден ключ по альтернативному поиску: {key_file}")
else:
print(f"Файл ключа не найден в {windows_certs_path}")
return False
# Копируем и переименовываем в директорию проекта
target_cert = wsl_certs_path / "wildcard.local.pem"
target_key = wsl_certs_path / "wildcard.local.key"
try:
shutil.copy2(cert_file, target_cert)
shutil.copy2(key_file, target_key)
print(f"Сертификат скопирован в: {target_cert}")
print(f"Ключ скопирован в: {target_key}")
except Exception as e:
print(f"Ошибка копирования сертификатов: {e}")
return False
# Создаем копии для default (если требуются контейнером)
default_cert = wsl_certs_path / "default.crt"
default_key = wsl_certs_path / "default.key"
try:
shutil.copy2(target_cert, default_cert)
shutil.copy2(target_key, default_key)
print(f"Копии сертификатов созданы как default.crt и default.key")
except Exception as e:
print(f"Ошибка создания копий сертификатов по умолчанию: {e}")
# Это может быть не критичной ошибкой, в зависимости от настройки контейнера
print(f"Сертификаты скопированы в {wsl_certs_path}")
return True
def main():
print("Обновление сертификатов и перезапуск сервисов...")
# Получаем путь к проекту
project_path = get_project_root()
# Генерируем конфигурацию dnsmasq перед обновлением
generate_dnsmasq_config(project_path)
# Выводим команду для генерации сертификатов
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(project_path):
print("Не удалось скопировать сертификаты")
sys.exit(1)
# Перезапускаем контейнеры Docker Compose
print("Перезапуск контейнеров Docker Compose...")
if not run_docker_command("docker-compose restart"):
print("Команда 'docker-compose restart' не удалась.")
print("Попытка поднять контейнеры с помощью 'docker-compose up -d'...")
if not run_docker_command("docker-compose up -d --force-recreate"):
print("Не удалось поднять контейнеры с помощью 'docker-compose up -d'.")
sys.exit(1)
else:
print("Контейнеры подняты с помощью 'docker-compose up -d'.")
else:
print("Контейнеры успешно перезапущены!")
print("Сертификаты обновлены и сервисы перезапущены!")
if __name__ == "__main__":
main()

View File

@@ -1,142 +0,0 @@
# 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'):
# Возвращаем первый найденный nameserver как IP хоста
# (Это может быть не всегда точно, но подходит для базового сценария)
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 по умолчанию, если не удалось получить
# Используем IP из памяти, если доступен
return "192.168.31.60"
def get_project_root():
"""Определяет корневую директорию проекта 'local-dns'."""
# Начинаем с домашней директории
current_path = Path.home() / "projects" / "docker"
project_name = "local-dns" # Используем фиксированное имя для этого репозитория
project_path = current_path / project_name
if not project_path.exists() or not (project_path / "docker-compose.yml").exists():
print(f"Директория проекта '{project_name}' или 'docker-compose.yml' не найдена в {current_path}")
sys.exit(1)
return project_path
def read_services_config(project_root):
"""Читает конфигурацию сервисов из файла в корне проекта."""
config_path = project_root / "services.json"
default_config = {
"services": [
{"name": "gitea", "port": 3000},
{"name": "youtrack", "port": 8080},
{"name": "n8n", "port": 5678}
]
}
if config_path.exists():
try:
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
print(f"Ошибка чтения JSON из {config_path}: {e}")
print("Используем конфигурацию по умолчанию.")
return default_config
except Exception as e:
print(f"Не удалось открыть файл конфигурации: {e}")
sys.exit(1)
else:
# Создаем файл конфигурации по умолчанию в корне проекта
try:
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(default_config, f, indent=2, ensure_ascii=False)
print(f"Файл конфигурации создан по умолчанию: {config_path}")
except Exception as e:
print(f"Не удалось создать файл конфигурации: {e}")
sys.exit(1)
return default_config
def generate_dnsmasq_config(project_root):
"""Генерирует конфигурационный файл dnsmasq в директории проекта."""
host_ip = get_host_ip()
config_path = project_root / "dnsmasq.conf"
services = read_services_config(project_root)
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
"""
try:
with open(config_path, 'w', encoding='utf-8') as f:
f.write(config_content.strip())
print(f"Конфигурационный файл dnsmasq обновлен: {config_path}")
except Exception as e:
print(f"Не удалось записать файл конфигурации dnsmasq: {e}")
sys.exit(1)
def run_docker_command(cmd):
"""Выполняет команду Docker и возвращает результат"""
try:
print(f"Выполняю команду: {cmd}")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f"Ошибка выполнения команды: {cmd}")
print(f"stderr: {result.stderr}")
print(f"stdout: {result.stdout}")
return False
print(f"Команда выполнена успешно: {cmd}")
return True
except Exception as e:
print(f"Ошибка выполнения команды: {e}")
return False
def setup_project():
"""Настройка проекта: генерация конфигов и переход в директорию проекта."""
project_path = get_project_root()
print(f"Работаем в директории проекта: {project_path}")
# Генерируем конфигурационный файл dnsmasq
generate_dnsmasq_config(project_path)
# Переходим в директорию проекта
os.chdir(project_path)
print(f"Текущая директория изменена на: {os.getcwd()}")
return project_path

View File

@@ -1,26 +0,0 @@
# restart_services.py
from common import *
def main():
print("Перезапуск сервисов Docker Compose...")
# Настройка проекта (включает генерацию конфига dnsmasq)
project_path = setup_project() # Вызывает setup_project, который включает generate_dnsmasq_config
# Перезапускаем контейнеры
print("Перезапуск контейнеров Docker Compose...")
if not run_docker_command("docker-compose restart"):
print("Команда 'docker-compose restart' не удалась.")
print("Попытка поднять контейнеры с помощью 'docker-compose up -d'...")
if not run_docker_command("docker-compose up -d"):
print("Не удалось поднять контейнеры с помощью 'docker-compose up -d'.")
sys.exit(1)
else:
print("Контейнеры подняты с помощью 'docker-compose up -d'.")
else:
print("Контейнеры успешно перезапущены!")
print("Сервисы перезапущены!")
if __name__ == "__main__":
main()