- Home »

Как создать CLI приложение с Python Fire на Ubuntu 24
Если ты хоть раз сталкивался с необходимостью создать CLI-утилиту для автоматизации задач на сервере, то знаешь, что процесс может быть довольно болезненным. Особенно когда нужно парсить аргументы, создавать help-сообщения и поддерживать множество команд. Python Fire — это настоящий game-changer в этой области. Эта библиотека превращает любую Python-функцию, класс или модуль в полноценное CLI-приложение буквально одной строкой кода.
Сегодня разберём, как создать мощное CLI-приложение с Python Fire на Ubuntu 24. Это особенно актуально для системных администраторов и DevOps-инженеров, которые ежедневно работают с серверами и нуждаются в быстрых инструментах для автоматизации. Рассмотрим реальные примеры: от простых утилит мониторинга до сложных систем управления сервисами.
Что такое Python Fire и почему это круто
Python Fire — это библиотека от Google, которая автоматически генерирует CLI-интерфейс для любого Python-объекта. Никаких argparse, click или других сложных парсеров аргументов. Просто добавляешь fire.Fire()
в конец скрипта, и вуаля — у тебя есть CLI.
Основные преимущества:
- Минимальный код — буквально одна строка для создания CLI
- Автоматическая документация — help генерируется из docstring’ов
- Поддержка вложенных команд — можно создавать сложные иерархии команд
- Типизация out-of-the-box — Fire автоматически преобразует типы данных
- Интерактивный режим — можно запускать команды в интерактивном shell
Настройка окружения на Ubuntu 24
Начнём с установки всего необходимого. Ubuntu 24 поставляется с Python 3.12, что отлично подходит для наших задач.
# Обновляем систему
sudo apt update && sudo apt upgrade -y
# Устанавливаем pip и venv (если ещё не установлены)
sudo apt install python3-pip python3-venv -y
# Создаём виртуальное окружение для проекта
python3 -m venv ~/cli-fire-env
source ~/cli-fire-env/bin/activate
# Устанавливаем Fire
pip install fire
# Для более продвинутых примеров установим дополнительные пакеты
pip install requests psutil colorama rich
Простой пример: утилита для мониторинга сервера
Создадим первую CLI-утилиту для мониторинга системных ресурсов. Это типичная задача для системного администратора.
# server_monitor.py
import fire
import psutil
import platform
from datetime import datetime
class ServerMonitor:
"""Утилита для мониторинга сервера"""
def cpu(self):
"""Показывает загрузку CPU"""
cpu_percent = psutil.cpu_percent(interval=1)
cpu_count = psutil.cpu_count()
print(f"CPU Usage: {cpu_percent}%")
print(f"CPU Cores: {cpu_count}")
return cpu_percent
def memory(self):
"""Показывает использование памяти"""
memory = psutil.virtual_memory()
print(f"Memory Usage: {memory.percent}%")
print(f"Total Memory: {memory.total / (1024**3):.2f} GB")
print(f"Available Memory: {memory.available / (1024**3):.2f} GB")
return memory.percent
def disk(self, path='/'):
"""Показывает использование диска"""
disk = psutil.disk_usage(path)
print(f"Disk Usage ({path}): {disk.percent}%")
print(f"Total Space: {disk.total / (1024**3):.2f} GB")
print(f"Free Space: {disk.free / (1024**3):.2f} GB")
return disk.percent
def system_info(self):
"""Показывает общую информацию о системе"""
print(f"System: {platform.system()}")
print(f"Release: {platform.release()}")
print(f"Version: {platform.version()}")
print(f"Machine: {platform.machine()}")
print(f"Processor: {platform.processor()}")
print(f"Uptime: {datetime.now() - datetime.fromtimestamp(psutil.boot_time())}")
def full_report(self):
"""Полный отчёт о состоянии системы"""
print("=== SERVER MONITORING REPORT ===")
print(f"Generated at: {datetime.now()}")
print("\n--- System Information ---")
self.system_info()
print("\n--- CPU Information ---")
self.cpu()
print("\n--- Memory Information ---")
self.memory()
print("\n--- Disk Information ---")
self.disk()
if __name__ == '__main__':
fire.Fire(ServerMonitor)
Теперь можно использовать утилиту разными способами:
# Запуск отдельных команд
python server_monitor.py cpu
python server_monitor.py memory
python server_monitor.py disk --path /home
# Полный отчёт
python server_monitor.py full_report
# Получить help
python server_monitor.py --help
python server_monitor.py cpu --help
Продвинутый пример: система управления сервисами
Создадим более сложную утилиту для управления системными сервисами. Это реальный инструмент, который можно использовать в production-окружении.
# service_manager.py
import fire
import subprocess
import json
import os
from datetime import datetime
from pathlib import Path
class ServiceManager:
"""Менеджер системных сервисов"""
def __init__(self, log_file='/var/log/service_manager.log'):
self.log_file = log_file
def _log(self, message):
"""Логирование операций"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
with open(self.log_file, 'a') as f:
f.write(f"[{timestamp}] {message}\n")
def _run_command(self, command):
"""Выполнение системной команды"""
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.returncode == 0, result.stdout, result.stderr
except Exception as e:
return False, "", str(e)
def status(self, service_name):
"""Проверка статуса сервиса"""
success, stdout, stderr = self._run_command(f"systemctl is-active {service_name}")
if success:
print(f"Service {service_name} is active")
else:
print(f"Service {service_name} is not active")
# Показываем детальную информацию
self._run_command(f"systemctl status {service_name}")
return success
def start(self, service_name):
"""Запуск сервиса"""
success, stdout, stderr = self._run_command(f"sudo systemctl start {service_name}")
if success:
print(f"Service {service_name} started successfully")
self._log(f"Started service: {service_name}")
else:
print(f"Failed to start service {service_name}: {stderr}")
self._log(f"Failed to start service: {service_name} - {stderr}")
return success
def stop(self, service_name):
"""Остановка сервиса"""
success, stdout, stderr = self._run_command(f"sudo systemctl stop {service_name}")
if success:
print(f"Service {service_name} stopped successfully")
self._log(f"Stopped service: {service_name}")
else:
print(f"Failed to stop service {service_name}: {stderr}")
self._log(f"Failed to stop service: {service_name} - {stderr}")
return success
def restart(self, service_name):
"""Перезапуск сервиса"""
success, stdout, stderr = self._run_command(f"sudo systemctl restart {service_name}")
if success:
print(f"Service {service_name} restarted successfully")
self._log(f"Restarted service: {service_name}")
else:
print(f"Failed to restart service {service_name}: {stderr}")
self._log(f"Failed to restart service: {service_name} - {stderr}")
return success
def enable(self, service_name):
"""Включение автозапуска сервиса"""
success, stdout, stderr = self._run_command(f"sudo systemctl enable {service_name}")
if success:
print(f"Service {service_name} enabled for autostart")
self._log(f"Enabled service: {service_name}")
else:
print(f"Failed to enable service {service_name}: {stderr}")
return success
def list_services(self, active_only=False):
"""Список всех сервисов"""
if active_only:
command = "systemctl list-units --type=service --state=active"
else:
command = "systemctl list-units --type=service --all"
success, stdout, stderr = self._run_command(command)
if success:
print(stdout)
else:
print(f"Failed to list services: {stderr}")
return success
def logs(self, service_name, lines=50):
"""Просмотр логов сервиса"""
command = f"sudo journalctl -u {service_name} -n {lines}"
success, stdout, stderr = self._run_command(command)
if success:
print(stdout)
else:
print(f"Failed to get logs for {service_name}: {stderr}")
return success
def health_check(self, services):
"""Проверка состояния нескольких сервисов"""
if isinstance(services, str):
services = [services]
results = {}
for service in services:
success, stdout, stderr = self._run_command(f"systemctl is-active {service}")
results[service] = "active" if success else "inactive"
print("Health Check Results:")
for service, status in results.items():
status_symbol = "✓" if status == "active" else "✗"
print(f"{status_symbol} {service}: {status}")
return results
class ServiceConfig:
"""Управление конфигурацией сервисов"""
def backup(self, service_name, backup_dir="/tmp/service_backups"):
"""Создание резервной копии конфигурации сервиса"""
os.makedirs(backup_dir, exist_ok=True)
# Находим конфигурационные файлы
config_paths = [
f"/etc/systemd/system/{service_name}.service",
f"/lib/systemd/system/{service_name}.service",
f"/usr/lib/systemd/system/{service_name}.service"
]
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_path = f"{backup_dir}/{service_name}_{timestamp}"
for config_path in config_paths:
if os.path.exists(config_path):
subprocess.run(f"cp {config_path} {backup_path}.service", shell=True)
print(f"Backed up {config_path} to {backup_path}.service")
return True
print(f"No configuration file found for service {service_name}")
return False
def edit(self, service_name):
"""Редактирование конфигурации сервиса"""
subprocess.run(f"sudo systemctl edit {service_name}", shell=True)
print(f"Configuration for {service_name} opened in editor")
class ServiceManagerCLI:
"""Основной CLI-интерфейс"""
def __init__(self):
self.service = ServiceManager()
self.config = ServiceConfig()
if __name__ == '__main__':
fire.Fire(ServiceManagerCLI)
Примеры использования:
# Управление сервисами
python service_manager.py service start nginx
python service_manager.py service stop apache2
python service_manager.py service restart mysql
# Проверка состояния
python service_manager.py service status nginx
python service_manager.py service health_check nginx,mysql,redis
# Работа с конфигурацией
python service_manager.py config backup nginx
python service_manager.py config edit nginx
# Просмотр логов
python service_manager.py service logs nginx --lines 100
Сравнение с другими решениями
Решение | Простота использования | Функциональность | Размер кода | Производительность |
---|---|---|---|---|
Python Fire | Очень высокая | Высокая | Минимальный | Хорошая |
argparse | Низкая | Очень высокая | Большой | Отличная |
Click | Средняя | Высокая | Средний | Хорошая |
Typer | Высокая | Высокая | Средний | Хорошая |
Интеграция с системами мониторинга
Создадим утилиту, которая интегрируется с популярными системами мониторинга:
# monitoring_integration.py
import fire
import requests
import json
import psutil
from datetime import datetime
class MonitoringIntegration:
"""Интеграция с системами мониторинга"""
def __init__(self, prometheus_gateway=None, grafana_url=None):
self.prometheus_gateway = prometheus_gateway
self.grafana_url = grafana_url
def send_to_prometheus(self, metric_name, value, labels=None):
"""Отправка метрик в Prometheus через Push Gateway"""
if not self.prometheus_gateway:
print("Prometheus Gateway URL not configured")
return False
labels = labels or {}
job_name = labels.get('job', 'python_cli')
metric_data = f"{metric_name} {value}\n"
try:
response = requests.post(
f"{self.prometheus_gateway}/metrics/job/{job_name}",
data=metric_data,
headers={'Content-Type': 'text/plain'}
)
if response.status_code == 200:
print(f"Metric {metric_name} sent to Prometheus")
return True
else:
print(f"Failed to send metric: {response.status_code}")
return False
except Exception as e:
print(f"Error sending to Prometheus: {e}")
return False
def collect_and_send_metrics(self):
"""Сбор и отправка системных метрик"""
metrics = {
'cpu_usage_percent': psutil.cpu_percent(interval=1),
'memory_usage_percent': psutil.virtual_memory().percent,
'disk_usage_percent': psutil.disk_usage('/').percent,
'load_average_1m': psutil.getloadavg()[0],
'uptime_seconds': (datetime.now() - datetime.fromtimestamp(psutil.boot_time())).total_seconds()
}
for metric_name, value in metrics.items():
self.send_to_prometheus(metric_name, value)
print(f"Sent {len(metrics)} metrics to monitoring system")
return True
def alert_webhook(self, webhook_url, message, severity='warning'):
"""Отправка алерта через webhook"""
payload = {
'text': message,
'severity': severity,
'timestamp': datetime.now().isoformat(),
'hostname': psutil.os.uname().nodename
}
try:
response = requests.post(webhook_url, json=payload)
if response.status_code == 200:
print(f"Alert sent: {message}")
return True
else:
print(f"Failed to send alert: {response.status_code}")
return False
except Exception as e:
print(f"Error sending alert: {e}")
return False
class PerformanceProfiler:
"""Профилирование производительности"""
def benchmark_disk(self, test_file='/tmp/disk_benchmark', size_mb=100):
"""Тест производительности диска"""
import time
# Тест записи
start_time = time.time()
with open(test_file, 'wb') as f:
data = b'0' * (1024 * 1024) # 1MB
for _ in range(size_mb):
f.write(data)
write_time = time.time() - start_time
# Тест чтения
start_time = time.time()
with open(test_file, 'rb') as f:
while f.read(1024 * 1024):
pass
read_time = time.time() - start_time
# Удаляем тестовый файл
import os
os.remove(test_file)
write_speed = size_mb / write_time
read_speed = size_mb / read_time
print(f"Disk Write Speed: {write_speed:.2f} MB/s")
print(f"Disk Read Speed: {read_speed:.2f} MB/s")
return {
'write_speed_mb_s': write_speed,
'read_speed_mb_s': read_speed
}
def network_test(self, host='8.8.8.8', port=53, timeout=5):
"""Тест сетевого подключения"""
import socket
import time
start_time = time.time()
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
if result == 0:
latency = (time.time() - start_time) * 1000
print(f"Network latency to {host}:{port}: {latency:.2f}ms")
return True, latency
else:
print(f"Failed to connect to {host}:{port}")
return False, None
except Exception as e:
print(f"Network test error: {e}")
return False, None
class AdvancedMonitoringCLI:
"""Продвинутый CLI для мониторинга"""
def __init__(self):
self.monitoring = MonitoringIntegration()
self.profiler = PerformanceProfiler()
if __name__ == '__main__':
fire.Fire(AdvancedMonitoringCLI)
Автоматизация и интеграция с cron
Создадим утилиту для автоматизации задач и интеграции с cron:
# automation_helper.py
import fire
import subprocess
import json
import os
from datetime import datetime, timedelta
from pathlib import Path
class AutomationHelper:
"""Помощник для автоматизации задач"""
def __init__(self, config_dir='~/.automation_helper'):
self.config_dir = Path(config_dir).expanduser()
self.config_dir.mkdir(exist_ok=True)
self.config_file = self.config_dir / 'config.json'
self.load_config()
def load_config(self):
"""Загрузка конфигурации"""
if self.config_file.exists():
with open(self.config_file, 'r') as f:
self.config = json.load(f)
else:
self.config = {'tasks': {}, 'notifications': {}}
def save_config(self):
"""Сохранение конфигурации"""
with open(self.config_file, 'w') as f:
json.dump(self.config, f, indent=2)
def add_task(self, name, command, schedule, description=''):
"""Добавление задачи для автоматизации"""
task = {
'command': command,
'schedule': schedule,
'description': description,
'created_at': datetime.now().isoformat(),
'last_run': None,
'status': 'active'
}
self.config['tasks'][name] = task
self.save_config()
print(f"Task '{name}' added successfully")
# Добавляем в crontab
self.add_to_cron(name, command, schedule)
def add_to_cron(self, name, command, schedule):
"""Добавление задачи в crontab"""
# Создаём обёртку для логирования
wrapper_script = self.config_dir / f'{name}_wrapper.sh'
log_file = self.config_dir / f'{name}.log'
wrapper_content = f"""#!/bin/bash
echo "$(date): Starting task {name}" >> {log_file}
{command} >> {log_file} 2>&1
echo "$(date): Task {name} completed with exit code $?" >> {log_file}
"""
with open(wrapper_script, 'w') as f:
f.write(wrapper_content)
os.chmod(wrapper_script, 0o755)
# Добавляем в crontab
cron_line = f"{schedule} {wrapper_script}"
subprocess.run(f'(crontab -l 2>/dev/null; echo "{cron_line}") | crontab -', shell=True)
print(f"Added to crontab: {cron_line}")
def remove_task(self, name):
"""Удаление задачи"""
if name in self.config['tasks']:
del self.config['tasks'][name]
self.save_config()
# Удаляем из crontab
self.remove_from_cron(name)
print(f"Task '{name}' removed successfully")
else:
print(f"Task '{name}' not found")
def remove_from_cron(self, name):
"""Удаление задачи из crontab"""
wrapper_script = self.config_dir / f'{name}_wrapper.sh'
if wrapper_script.exists():
wrapper_script.unlink()
# Удаляем из crontab
subprocess.run(f'crontab -l | grep -v "{name}_wrapper.sh" | crontab -', shell=True)
def list_tasks(self):
"""Список всех задач"""
if not self.config['tasks']:
print("No tasks configured")
return
print("Configured tasks:")
for name, task in self.config['tasks'].items():
print(f"\n--- {name} ---")
print(f"Command: {task['command']}")
print(f"Schedule: {task['schedule']}")
print(f"Description: {task['description']}")
print(f"Status: {task['status']}")
print(f"Created: {task['created_at']}")
# Показываем последние логи
log_file = self.config_dir / f'{name}.log'
if log_file.exists():
print("Last log entries:")
subprocess.run(f'tail -n 5 {log_file}', shell=True)
def run_task(self, name):
"""Запуск задачи вручную"""
if name not in self.config['tasks']:
print(f"Task '{name}' not found")
return False
task = self.config['tasks'][name]
print(f"Running task: {name}")
try:
result = subprocess.run(task['command'], shell=True, capture_output=True, text=True)
# Обновляем информацию о последнем запуске
self.config['tasks'][name]['last_run'] = datetime.now().isoformat()
self.save_config()
if result.returncode == 0:
print(f"Task '{name}' completed successfully")
print("Output:", result.stdout)
else:
print(f"Task '{name}' failed with exit code {result.returncode}")
print("Error:", result.stderr)
return result.returncode == 0
except Exception as e:
print(f"Error running task '{name}': {e}")
return False
def setup_monitoring(self, check_interval=300):
"""Настройка мониторинга выполнения задач"""
monitor_script = self.config_dir / 'monitor.py'
monitor_content = f"""#!/usr/bin/env python3
import json
import os
import time
from datetime import datetime, timedelta
from pathlib import Path
config_dir = Path('{self.config_dir}')
config_file = config_dir / 'config.json'
while True:
if config_file.exists():
with open(config_file, 'r') as f:
config = json.load(f)
for name, task in config['tasks'].items():
log_file = config_dir / f'{{name}}.log'
if log_file.exists():
# Проверяем последнюю запись в логе
lines = log_file.read_text().strip().split('\\n')
if lines:
last_line = lines[-1]
if 'failed' in last_line.lower() or 'error' in last_line.lower():
print(f"ALERT: Task {{name}} failed - {{last_line}}")
time.sleep({check_interval})
"""
with open(monitor_script, 'w') as f:
f.write(monitor_content)
os.chmod(monitor_script, 0o755)
print(f"Monitoring script created: {monitor_script}")
if __name__ == '__main__':
fire.Fire(AutomationHelper)
Практические советы и best practices
- Используйте docstrings — Fire автоматически генерирует help из документации функций
- Типизируйте аргументы — Fire поддерживает type hints для автоматического преобразования типов
- Группируйте команды в классы — это создаёт логическую структуру CLI
- Добавляйте логирование — особенно важно для production-утилит
- Обрабатывайте исключения — пользователи CLI не должны видеть traceback’и
- Используйте конфигурационные файлы — для сложных утилит с множеством параметров
Интересные возможности и трюки
Fire предоставляет несколько крутых возможностей, которые не всегда очевидны:
Интерактивный режим
# Запуск в интерактивном режиме
python service_manager.py -- --interactive
# Теперь можно вводить команды интерактивно
> service status nginx
> service restart mysql
> config backup nginx
Сериализация в JSON
# Получение результата в JSON
python server_monitor.py cpu -- --serialize
# Это полезно для интеграции с другими системами
Поддержка *args и **kwargs
def deploy_service(service_name, *environments, **config):
"""Развёртывание сервиса в нескольких окружениях"""
for env in environments:
print(f"Deploying {service_name} to {env}")
for key, value in config.items():
print(f" {key}: {value}")
# Использование:
python deploy.py deploy_service myapp dev staging prod --version 1.2.3 --replicas 3
Создание исполняемых файлов
Для удобства можно создать исполняемые файлы с shebang:
#!/usr/bin/env python3
# server_monitor
import fire
# ... остальной код ...
if __name__ == '__main__':
fire.Fire(ServerMonitor)
# Делаем файл исполняемым
chmod +x server_monitor
# Теперь можно запускать напрямую
./server_monitor cpu
./server_monitor memory
Интеграция с системными службами
Для серверных утилит часто нужна интеграция с systemd. Создадим service-файл:
# /etc/systemd/system/server-monitor.service
[Unit]
Description=Server Monitor Service
After=network.target
[Service]
Type=simple
User=monitor
ExecStart=/usr/local/bin/server_monitor daemon --interval 60
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
# Добавляем daemon-режим в наш код
def daemon(self, interval=60):
"""Запуск в режиме демона"""
import time
while True:
try:
self.collect_and_send_metrics()
time.sleep(interval)
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error in daemon mode: {e}")
time.sleep(10)
Производительность и оптимизация
Fire добавляет небольшой overhead при запуске, но для большинства CLI-утилит это незаметно. Если нужна максимальная производительность:
- Ленивая инициализация — не загружайте тяжёлые модули в начале
- Кэширование — используйте @lru_cache для дорогих операций
- Асинхронность — Fire работает с async/await функциями
import asyncio
import fire
class AsyncCLI:
async def fetch_data(self, url):
"""Асинхронная загрузка данных"""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
if __name__ == '__main__':
fire.Fire(AsyncCLI)
Тестирование CLI-приложений
Создадим тесты для наших утилит:
# test_cli.py
import unittest
from unittest.mock import patch, MagicMock
from server_monitor import ServerMonitor
class TestServerMonitor(unittest.TestCase):
def setUp(self):
self.monitor = ServerMonitor()
@patch('psutil.cpu_percent')
def test_cpu_monitoring(self, mock_cpu):
mock_cpu.return_value = 45.5
result = self.monitor.cpu()
self.assertEqual(result, 45.5)
@patch('psutil.virtual_memory')
def test_memory_monitoring(self, mock_memory):
mock_memory.return_value = MagicMock(percent=75.0)
result = self.monitor.memory()
self.assertEqual(result, 75.0)
if __name__ == '__main__':
unittest.main()
Заключение
Python Fire — это мощный инструмент для создания CLI-приложений, который значительно упрощает разработку административных утилит. Особенно полезен для системных администраторов и DevOps-инженеров, которым нужно быстро создавать инструменты для автоматизации.
Основные преимущества Fire:
- Скорость разработки — от идеи до рабочего CLI за минуты
- Простота поддержки — минимальный код для поддержки
- Гибкость — подходит как для простых скриптов, так и для сложных систем
- Интеграция — легко интегрируется с существующими системами
Fire особенно рекомендую для:
- Утилит мониторинга и диагностики серверов
- Инструментов автоматизации deployment’а
- Систем управления конфигурациями
- Интеграционных скриптов между различными системами
- Прототипирования сложных CLI-интерфейсов
Если тебе нужен VPS для тестирования и развёртывания твоих CLI-утилит, рекомендую аренду VPS с Ubuntu 24. Для более серьёзных нагрузок и production-окружений подойдёт выделенный сервер.
Fire превращает создание CLI из рутинной задачи в увлекательный процесс. Попробуй создать свою первую утилиту — увидишь, насколько это просто и удобно!
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.