docs: update README and docs with strict Quick Start (docker-compose-prod), fix WebSocket paths, enforce strict tone
This commit is contained in:
parent
afa2829872
commit
7ccdf75bab
14
Dockerfile
14
Dockerfile
@ -3,8 +3,20 @@ FROM python:3.11-slim
|
||||
ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1
|
||||
WORKDIR /app
|
||||
|
||||
# Устанавливаем системные зависимости для сборки некоторых Python пакетов
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
gcc \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
cargo \
|
||||
rustc && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN python -m pip install --upgrade pip setuptools wheel && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY ./app /app
|
||||
|
||||
|
27
Makefile
27
Makefile
@ -1,12 +1,15 @@
|
||||
.ONESHELL:
|
||||
# Makefile для LogBoard+
|
||||
# Автор: Сергей Антропов
|
||||
# Сайт: https://devops.org.ru
|
||||
|
||||
.PHONY: help setup build up down restart logs clean status ps shell
|
||||
.PHONY: help setup build up down restart logs clean status ps shell release release-compose
|
||||
|
||||
# Переменные
|
||||
COMPOSE_FILE = docker-compose.yml
|
||||
SERVICE_NAME = logboard
|
||||
COMPOSE_PROD_FILE = docker-compose-prod.yaml
|
||||
PLATFORMS ?= linux/amd64,linux/arm64
|
||||
|
||||
# Цвета для вывода
|
||||
GREEN = \033[0;32m
|
||||
@ -165,3 +168,25 @@ debug-status: ## Показать статус режима отладки
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# release: Сборка и публикация образа в Docker Registry + генерация compose для продакшена
|
||||
# Описание:
|
||||
# - Запрашивает реестр, логин и пароль
|
||||
# - Собирает образ, тегирует и пушит
|
||||
# - Генерирует файл $(COMPOSE_PROD_FILE) для запуска в продакшене
|
||||
# Пример:
|
||||
# make release
|
||||
# -----------------------------------------------------------------------------
|
||||
release: ## Собрать, залогиниться, запушить образ и сгенерировать docker-compose-prod.yaml
|
||||
@echo "$(GREEN)Release: сборка и публикация образа$(NC)"
|
||||
@python3 release/publish_image.py --platforms "$(PLATFORMS)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# release-compose: Генерация docker-compose-prod.yaml с подстановкой переменных
|
||||
# -----------------------------------------------------------------------------
|
||||
release-compose: ## Сгенерировать docker-compose-prod.yaml с переменными
|
||||
@echo "$(YELLOW)Генерация файла $(COMPOSE_PROD_FILE) для продакшена...$(NC)"
|
||||
@python3 release/generate_compose.py --env env.example --template release/docker-compose-prod.tmpl.yaml --output $(COMPOSE_PROD_FILE)
|
||||
|
||||
|
||||
|
135
README.md
135
README.md
@ -15,7 +15,7 @@
|
||||
|
||||
LogBoard+ - это современная веб-панель для мониторинга и просмотра логов Docker контейнеров в реальном времени. Приложение идеально подходит для локальной разработки, позволяя разработчикам всегда держать логи микросервисов перед глазами на втором мониторе.
|
||||
|
||||
### 🎯 **Идеально для локальной разработки**
|
||||
### Идеально для локальной разработки
|
||||
|
||||
LogBoard+ особенно полезен для разработчиков, работающих с микросервисной архитектурой:
|
||||
|
||||
@ -24,7 +24,7 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
- **Мониторинг в реальном времени** - Видите проблемы сразу, как они возникают
|
||||
- **Централизованный просмотр** - Все логи в одном месте, а не в десятках терминалов
|
||||
|
||||
### 🐳 **Оптимизирован для Docker и Docker Compose**
|
||||
### Оптимизирован для Docker и Docker Compose
|
||||
|
||||
Если ваша инфраструктура основана на Docker и Docker Compose, LogBoard+ станет незаменимым инструментом:
|
||||
|
||||
@ -34,7 +34,7 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
- **Multi-view режим** - одновременный просмотр логов нескольких контейнеров
|
||||
- **Интеграция с Docker API** - прямая работа с контейнерами
|
||||
|
||||
### 🚀 **Производительность и удобство**
|
||||
### Производительность и удобство
|
||||
|
||||
Приложение предоставляет удобный веб-интерфейс для работы с логами микросервисов, поддерживает множественные проекты Docker Compose и включает в себя функции безопасности.
|
||||
|
||||
@ -51,35 +51,35 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
|
||||
## Скриншоты
|
||||
|
||||
### 🔐 Страница входа
|
||||
### Страница входа
|
||||
| Светлая тема | Темная тема |
|
||||
|--------------|-------------|
|
||||
|  |  |
|
||||
|
||||
### 📊 Основной интерфейс
|
||||
### Основной интерфейс
|
||||
| Светлая тема | Темная тема |
|
||||
|--------------|-------------|
|
||||
|  |  |
|
||||
|
||||
### 🖥️ Multi-view режим
|
||||
### Multi-view режим
|
||||

|
||||
|
||||
### 📋 Карточки контейнеров
|
||||
### Карточки контейнеров
|
||||

|
||||
|
||||
### 📁 Проекты
|
||||
### Проекты
|
||||

|
||||
|
||||
### ⚙️ Настройки
|
||||
### Настройки
|
||||

|
||||
|
||||
### 🔧 Сворачиваемая боковая панель
|
||||
### Сворачиваемая боковая панель
|
||||

|
||||
|
||||
### ❓ Справка
|
||||
### Справка
|
||||

|
||||
|
||||
### 🚨 Страницы ошибок
|
||||
### Страницы ошибок
|
||||

|
||||
|
||||
## Быстрый старт
|
||||
@ -91,7 +91,7 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
- 1 GB RAM
|
||||
- 1 CPU core
|
||||
|
||||
### Установка и запуск
|
||||
### Вариант A: Локальный запуск (разработка)
|
||||
|
||||
1. **Клонирование репозитория**
|
||||
```bash
|
||||
@ -99,17 +99,6 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
cd logboard
|
||||
```
|
||||
|
||||
2. **Настройка переменных окружения**
|
||||
```bash
|
||||
cp env.example .env
|
||||
# Отредактируйте .env файл при необходимости
|
||||
```
|
||||
|
||||
3. **Запуск приложения**
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
2. **Настройка переменных окружения**
|
||||
```bash
|
||||
make setup
|
||||
@ -126,6 +115,88 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
http://localhost:9001
|
||||
```
|
||||
|
||||
### Вариант B: Продакшен через docker-compose-prod.yaml
|
||||
|
||||
Ниже приведен пример конфигурации из `docker-compose-prod.yaml` для запуска готового образа в продакшене:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
logboard:
|
||||
image: docker.io/inecs/logboard:v1
|
||||
container_name: logboard
|
||||
environment:
|
||||
# ОСНОВНЫЕ НАСТРОЙКИ ПРИЛОЖЕНИЯ
|
||||
LOGBOARD_PORT: "9001"
|
||||
LOGBOARD_TAIL: "500"
|
||||
LOGBOARD_USER: "admin"
|
||||
LOGBOARD_PASS: "admin"
|
||||
LOGBOARD_SNAPSHOT_DIR: "/app/snapshots"
|
||||
LOGBOARD_STATIC_DIR: "/app/static"
|
||||
LOGBOARD_INDEX_HTML: "./app/templates/index.html"
|
||||
TZ_TS: "Europe/Moscow"
|
||||
|
||||
# НАСТРОЙКИ DOCKER
|
||||
DOCKER_HOST: "unix:///var/run/docker.sock"
|
||||
DOCKER_TLS_VERIFY: ""
|
||||
DOCKER_CERT_PATH: ""
|
||||
DOCKER_NETWORKS: "iaas,infrastructure_iaas"
|
||||
|
||||
# БЕЗОПАСНОСТЬ
|
||||
SECRET_KEY: "your-secret-key-here"
|
||||
ENCRYPTION_KEY: "your-encryption-key-here"
|
||||
|
||||
# ЛОГИРОВАНИЕ
|
||||
LOG_LEVEL: "INFO"
|
||||
LOG_FORMAT: "json"
|
||||
|
||||
# ВЕБ-ИНТЕРФЕЙС
|
||||
WEB_TITLE: "LogBoard+"
|
||||
WEB_DESCRIPTION: "Веб-панель для просмотра логов микросервисов"
|
||||
WEB_VERSION: "1.0.0"
|
||||
|
||||
# РЕЖИМ РАЗРАБОТКИ
|
||||
DEBUG_MODE: "false"
|
||||
|
||||
# ПРОИЗВОДИТЕЛЬНОСТЬ
|
||||
MAX_CONNECTIONS: "100"
|
||||
CONNECTION_TIMEOUT: "30"
|
||||
READ_TIMEOUT: "60"
|
||||
|
||||
# ФИЛЬТРАЦИЯ КОНТЕЙНЕРОВ
|
||||
LOGBOARD_SKIP_UNHEALTHY: "true"
|
||||
LOGBOARD_CONTAINER_LIST_TIMEOUT: "10"
|
||||
LOGBOARD_CONTAINER_INFO_TIMEOUT: "3"
|
||||
LOGBOARD_HEALTH_CHECK_TIMEOUT: "2"
|
||||
|
||||
# АУТЕНТИФИКАЦИЯ
|
||||
AUTH_ENABLED: "true"
|
||||
AUTH_METHOD: "jwt"
|
||||
SESSION_TIMEOUT: "3600"
|
||||
|
||||
# УВЕДОМЛЕНИЯ
|
||||
NOTIFICATIONS_ENABLED: "false"
|
||||
SMTP_HOST: ""
|
||||
SMTP_PORT: "587"
|
||||
SMTP_USER: ""
|
||||
SMTP_PASS: ""
|
||||
SMTP_FROM: ""
|
||||
|
||||
# AJAX ОБНОВЛЕНИЕ
|
||||
LOGBOARD_AJAX_UPDATE_INTERVAL: "2000"
|
||||
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./snapshots:/app/snapshots
|
||||
restart: unless-stopped
|
||||
user: 0:0
|
||||
```
|
||||
|
||||
Рекомендации для продакшена:
|
||||
- Используйте файл `.env` для значений чувствительных переменных (`SECRET_KEY`, `ENCRYPTION_KEY`, `LOGBOARD_PASS`) и не храните их в открытом виде в compose-файле.
|
||||
- Настройте reverse proxy (Nginx/Traefik) и включите HTTPS.
|
||||
|
||||
### Учетные данные по умолчанию
|
||||
|
||||
- **Пользователь:** `admin`
|
||||
@ -231,23 +302,23 @@ logboard/
|
||||
|
||||
#### Контейнеры и сервисы
|
||||
|
||||
- `GET /api/services` - Список контейнеров
|
||||
- `GET /api/projects` - Список проектов Docker Compose
|
||||
- `GET /api/containers/services` - Список контейнеров
|
||||
- `GET /api/containers/projects` - Список проектов Docker Compose
|
||||
- `GET /api/logs/{container_id}` - Логи контейнера
|
||||
- `GET /api/logs/stats/{container_id}` - Статистика логов
|
||||
|
||||
#### Управление
|
||||
|
||||
- `GET /api/settings` - Настройки приложения
|
||||
- `GET /api/excluded-containers` - Список исключенных контейнеров
|
||||
- `POST /api/excluded-containers` - Обновление исключенных контейнеров
|
||||
- `POST /api/snapshot` - Создание снимка логов
|
||||
- `GET /api/containers/excluded` - Список исключенных контейнеров
|
||||
- `POST /api/containers/excluded` - Обновление исключенных контейнеров
|
||||
- `POST /api/logs/snapshot` - Создание снимка логов
|
||||
|
||||
### WebSocket API
|
||||
|
||||
- `ws://host:port/ws/logs/{container_id}` - Логи отдельного контейнера
|
||||
- `ws://host:port/ws/fan/{service_name}` - Логи сервиса (все реплики)
|
||||
- `ws://host:port/ws/fan_group` - Логи группы сервисов
|
||||
- `ws://host:port/api/websocket/logs/{container_id}` - Логи отдельного контейнера
|
||||
- `ws://host:port/api/websocket/fan/{service_name}` - Логи сервиса (все реплики)
|
||||
- `ws://host:port/api/websocket/fan_group` - Логи группы сервисов
|
||||
|
||||
## Конфигурация
|
||||
|
||||
|
84
app/scripts/substitute_env.py
Normal file
84
app/scripts/substitute_env.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт подстановки переменных окружения из .env/env.example в файл шаблона.
|
||||
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
|
||||
Использование:
|
||||
python3 app/scripts/substitute_env.py PATH_TO_ENV PATH_TO_COMPOSE_FILE
|
||||
|
||||
Логика:
|
||||
- Читает пары KEY=VALUE из указанного env-файла (игнорирует комментарии и пустые строки)
|
||||
- Не выполняет файл как shell (безопасно парсит)
|
||||
- Заменяет плейсхолдеры вида ${KEY} в целевом файле
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def parse_env_file(env_path: str) -> Dict[str, str]:
|
||||
"""Простой парсер .env файла: KEY=VALUE, без исполнения shell."""
|
||||
variables: Dict[str, str] = {}
|
||||
with io.open(env_path, "r", encoding="utf-8") as fh:
|
||||
for raw in fh:
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, val = line.split("=", 1)
|
||||
key = key.strip()
|
||||
val = val.strip()
|
||||
# Снимаем внешние кавычки, если присутствуют
|
||||
if (val.startswith('"') and val.endswith('"')) or (
|
||||
val.startswith("'") and val.endswith("'")
|
||||
):
|
||||
val = val[1:-1]
|
||||
variables[key] = val
|
||||
return variables
|
||||
|
||||
|
||||
def substitute_placeholders(text: str, env: Dict[str, str]) -> str:
|
||||
"""Заменяет ${VAR} из словаря env, оставляет плейсхолдер, если VAR не найден."""
|
||||
pattern = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||
return pattern.sub(lambda m: env.get(m.group(1), m.group(0)), text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write(
|
||||
"Usage: substitute_env.py <env_path> <compose_path>\n"
|
||||
)
|
||||
return 2
|
||||
|
||||
env_path, compose_path = sys.argv[1], sys.argv[2]
|
||||
|
||||
if not os.path.exists(env_path):
|
||||
sys.stderr.write(f"Env file not found: {env_path}\n")
|
||||
return 3
|
||||
if not os.path.exists(compose_path):
|
||||
sys.stderr.write(f"Compose file not found: {compose_path}\n")
|
||||
return 4
|
||||
|
||||
env = parse_env_file(env_path)
|
||||
|
||||
with io.open(compose_path, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
new_content = substitute_placeholders(content, env)
|
||||
with io.open(compose_path, "w", encoding="utf-8") as fh:
|
||||
fh.write(new_content)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
113
docker-compose-prod.yaml
Normal file
113
docker-compose-prod.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
services:
|
||||
logboard:
|
||||
image: docker.io/inecs/logboard:v1
|
||||
container_name: logboard
|
||||
environment:
|
||||
# ОСНОВНЫЕ НАСТРОЙКИ ПРИЛОЖЕНИЯ
|
||||
# Порт веб-интерфейса LogBoard+
|
||||
LOGBOARD_PORT: "9001"
|
||||
# Количество строк логов по умолчанию (tail)
|
||||
LOGBOARD_TAIL: "500"
|
||||
# Имя пользователя для входа
|
||||
LOGBOARD_USER: "admin"
|
||||
# Пароль для входа (обязательно поменять в продакшене)
|
||||
LOGBOARD_PASS: "admin"
|
||||
# Директория для снимков логов (в контейнере)
|
||||
LOGBOARD_SNAPSHOT_DIR: "/app/snapshots"
|
||||
# Директория для статических файлов
|
||||
LOGBOARD_STATIC_DIR: "/app/static"
|
||||
# Путь к HTML шаблону главной страницы
|
||||
LOGBOARD_INDEX_HTML: "./app/templates/index.html"
|
||||
# Таймзона для временных меток (например: Europe/Moscow, UTC)
|
||||
TZ_TS: "Europe/Moscow"
|
||||
|
||||
# НАСТРОЙКИ DOCKER
|
||||
# Фильтр по проекту Docker Compose (опционально)
|
||||
# COMPOSE_PROJECT_NAME: "${COMPOSE_PROJECT_NAME}"
|
||||
# Перечень проектов для отображения (через запятую)
|
||||
# LOGBOARD_PROJECTS: "${LOGBOARD_PROJECTS}"
|
||||
# Путь к Docker socket / удалённому хосту
|
||||
DOCKER_HOST: "unix:///var/run/docker.sock"
|
||||
# Включить проверку TLS для Docker (удалённые хосты)
|
||||
DOCKER_TLS_VERIFY: ""
|
||||
# Путь к TLS сертификатам Docker
|
||||
DOCKER_CERT_PATH: ""
|
||||
# Внешние сети Docker (через запятую)
|
||||
DOCKER_NETWORKS: "iaas,infrastructure_iaas"
|
||||
|
||||
# БЕЗОПАСНОСТЬ
|
||||
# Секретный ключ для JWT (обязательно поменять в продакшене)
|
||||
SECRET_KEY: "your-secret-key-here"
|
||||
# Ключ шифрования для чувствительных данных (обязательно поменять)
|
||||
ENCRYPTION_KEY: "your-encryption-key-here"
|
||||
|
||||
# ЛОГИРОВАНИЕ
|
||||
# Уровень логирования (DEBUG, INFO, WARNING, ERROR)
|
||||
LOG_LEVEL: "INFO"
|
||||
# Формат логов (json, text)
|
||||
LOG_FORMAT: "json"
|
||||
|
||||
# ВЕБ-ИНТЕРФЕЙС
|
||||
# Заголовок веб-интерфейса
|
||||
WEB_TITLE: "LogBoard+"
|
||||
# Описание веб-интерфейса
|
||||
WEB_DESCRIPTION: "Веб-панель для просмотра логов микросервисов"
|
||||
# Версия веб-интерфейса
|
||||
WEB_VERSION: "1.0.0"
|
||||
|
||||
# РЕЖИМ РАЗРАБОТКИ
|
||||
# Режим отладки (true/false)
|
||||
DEBUG_MODE: "false"
|
||||
|
||||
# ПРОИЗВОДИТЕЛЬНОСТЬ
|
||||
# Максимум одновременных подключений
|
||||
MAX_CONNECTIONS: "100"
|
||||
# Таймаут подключения (сек)
|
||||
CONNECTION_TIMEOUT: "30"
|
||||
# Таймаут чтения (сек)
|
||||
READ_TIMEOUT: "60"
|
||||
|
||||
# ФИЛЬТРАЦИЯ КОНТЕЙНЕРОВ
|
||||
# Пропускать контейнеры с проблемным health check (true/false)
|
||||
LOGBOARD_SKIP_UNHEALTHY: "true"
|
||||
# Таймаут получения списка контейнеров (сек)
|
||||
LOGBOARD_CONTAINER_LIST_TIMEOUT: "10"
|
||||
# Таймаут получения информации о контейнере (сек)
|
||||
LOGBOARD_CONTAINER_INFO_TIMEOUT: "3"
|
||||
# Таймаут health check контейнера (сек)
|
||||
LOGBOARD_HEALTH_CHECK_TIMEOUT: "2"
|
||||
|
||||
# АУТЕНТИФИКАЦИЯ
|
||||
# Включить аутентификацию (true/false)
|
||||
AUTH_ENABLED: "true"
|
||||
# Метод аутентификации (jwt)
|
||||
AUTH_METHOD: "jwt"
|
||||
# Время жизни сессии (сек)
|
||||
SESSION_TIMEOUT: "3600"
|
||||
|
||||
# УВЕДОМЛЕНИЯ
|
||||
# Включить уведомления по email (true/false)
|
||||
NOTIFICATIONS_ENABLED: "false"
|
||||
# SMTP сервер
|
||||
SMTP_HOST: ""
|
||||
# Порт SMTP
|
||||
SMTP_PORT: "587"
|
||||
# Пользователь SMTP
|
||||
SMTP_USER: ""
|
||||
# Пароль SMTP
|
||||
SMTP_PASS: ""
|
||||
# Email отправителя
|
||||
SMTP_FROM: ""
|
||||
|
||||
# AJAX ОБНОВЛЕНИЕ
|
||||
# Интервал AJAX обновления логов (мс)
|
||||
LOGBOARD_AJAX_UPDATE_INTERVAL: "2000"
|
||||
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./snapshots:/app/snapshots
|
||||
restart: unless-stopped
|
||||
user: 0:0
|
||||
|
36
docs/api.md
36
docs/api.md
@ -16,7 +16,7 @@
|
||||
|
||||
LogBoard+ предоставляет REST API и WebSocket API для работы с логами Docker контейнеров. API разработан для интеграции с системами мониторинга и автоматизации, а также для создания собственных клиентов.
|
||||
|
||||
### 🎯 **Применение API**
|
||||
### Применение API
|
||||
|
||||
- **Интеграция с CI/CD** - автоматический мониторинг логов в пайплайнах
|
||||
- **Собственные дашборды** - создание кастомных интерфейсов мониторинга
|
||||
@ -108,7 +108,7 @@ Authorization: Bearer <token>
|
||||
|
||||
### Контейнеры и сервисы
|
||||
|
||||
#### GET /api/services
|
||||
#### GET /api/containers/services
|
||||
|
||||
Получение списка всех контейнеров.
|
||||
|
||||
@ -122,7 +122,7 @@ Authorization: Bearer <token>
|
||||
**Пример запроса:**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:9001/api/services?projects=myproject&include_stopped=true" \
|
||||
curl -X GET "http://localhost:9001/api/containers/services?projects=myproject&include_stopped=true" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
@ -157,7 +157,7 @@ curl -X GET "http://localhost:9001/api/services?projects=myproject&include_stopp
|
||||
]
|
||||
```
|
||||
|
||||
#### GET /api/projects
|
||||
#### GET /api/containers/projects
|
||||
|
||||
Получение списка всех проектов Docker Compose.
|
||||
|
||||
@ -268,7 +268,7 @@ curl -X GET "http://localhost:9001/api/logs/abc123def456?tail=100&since=2024-01-
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /api/excluded-containers
|
||||
#### GET /api/containers/excluded
|
||||
|
||||
Получение списка исключенных контейнеров.
|
||||
|
||||
@ -283,7 +283,7 @@ curl -X GET "http://localhost:9001/api/logs/abc123def456?tail=100&since=2024-01-
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/excluded-containers
|
||||
#### POST /api/containers/excluded
|
||||
|
||||
Обновление списка исключенных контейнеров.
|
||||
|
||||
@ -306,7 +306,7 @@ curl -X GET "http://localhost:9001/api/logs/abc123def456?tail=100&since=2024-01-
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/snapshot
|
||||
#### POST /api/logs/snapshot
|
||||
|
||||
Создание снимка логов.
|
||||
|
||||
@ -374,7 +374,7 @@ ok
|
||||
|
||||
Все WebSocket endpoints требуют JWT токен в параметре `token`.
|
||||
|
||||
### ws://host:port/ws/logs/{container_id}
|
||||
### ws://host:port/api/websocket/logs/{container_id}
|
||||
|
||||
Получение логов отдельного контейнера.
|
||||
|
||||
@ -390,7 +390,7 @@ ok
|
||||
```javascript
|
||||
const token = "your-jwt-token";
|
||||
const containerId = "abc123def456";
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/logs/${containerId}?token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/logs/${containerId}?token=${token}&tail=100`);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
console.log('Получены логи:', event.data);
|
||||
@ -401,7 +401,7 @@ ws.onerror = function(error) {
|
||||
};
|
||||
```
|
||||
|
||||
### ws://host:port/ws/fan/{service_name}
|
||||
### ws://host:port/api/websocket/fan/{service_name}
|
||||
|
||||
Получение логов сервиса (все реплики).
|
||||
|
||||
@ -416,14 +416,14 @@ ws.onerror = function(error) {
|
||||
```javascript
|
||||
const token = "your-jwt-token";
|
||||
const serviceName = "web";
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/fan/${serviceName}?token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/fan/${serviceName}?token=${token}&tail=100`);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
console.log('Логи сервиса:', event.data);
|
||||
};
|
||||
```
|
||||
|
||||
### ws://host:port/ws/fan_group
|
||||
### ws://host:port/api/websocket/fan_group
|
||||
|
||||
Получение логов группы сервисов.
|
||||
|
||||
@ -438,7 +438,7 @@ ws.onmessage = function(event) {
|
||||
```javascript
|
||||
const token = "your-jwt-token";
|
||||
const services = "web,db,redis";
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/fan_group?services=${services}&token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/fan_group?services=${services}&token=${token}&tail=100`);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
console.log('Логи группы сервисов:', event.data);
|
||||
@ -522,7 +522,7 @@ def login(username, password):
|
||||
# Получение списка контейнеров
|
||||
def get_containers(token):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{BASE_URL}/api/services", headers=headers)
|
||||
response = requests.get(f"{BASE_URL}/api/containers/services", headers=headers)
|
||||
return response.json()
|
||||
|
||||
# Получение логов контейнера
|
||||
@ -563,7 +563,7 @@ class LogBoardAPI {
|
||||
if (projects) params.append('projects', projects);
|
||||
if (includeStopped) params.append('include_stopped', 'true');
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/api/services?${params}`, {
|
||||
const response = await fetch(`${this.baseUrl}/api/containers/services?${params}`, {
|
||||
headers: this.headers
|
||||
});
|
||||
return await response.json();
|
||||
@ -592,7 +592,7 @@ class LogBoardAPI {
|
||||
// WebSocket для live-логов
|
||||
createLogsWebSocket(containerId, tail = 100) {
|
||||
const ws = new WebSocket(
|
||||
`ws://${this.baseUrl.replace('http://', '')}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`
|
||||
`ws://${this.baseUrl.replace('http://', '')}/api/websocket/logs/${containerId}?token=${this.token}&tail=${tail}`
|
||||
);
|
||||
return ws;
|
||||
}
|
||||
@ -654,12 +654,12 @@ echo "Токен получен: ${TOKEN:0:20}..."
|
||||
|
||||
# Получение списка контейнеров
|
||||
echo "Получение списка контейнеров..."
|
||||
curl -s -X GET "$BASE_URL/api/services" \
|
||||
curl -s -X GET "$BASE_URL/api/containers/services" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
| jq '.[] | {name: .name, status: .status, project: .project}'
|
||||
|
||||
# Получение логов первого контейнера
|
||||
CONTAINER_ID=$(curl -s -X GET "$BASE_URL/api/services" \
|
||||
CONTAINER_ID=$(curl -s -X GET "$BASE_URL/api/containers/services" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
| jq -r '.[0].id')
|
||||
|
||||
|
@ -109,7 +109,7 @@ LogBoard+ использует переменные окружения для н
|
||||
- **ReDoc** - альтернативная документация по адресу `/redoc`
|
||||
- **Подробное логирование** - детальные логи для отладки
|
||||
|
||||
**⚠️ Важно:** В продакшене обязательно установите `DEBUG_MODE=false`!
|
||||
Важно: в продакшене обязательно установите `DEBUG_MODE=false`.
|
||||
|
||||
### Настройки уведомлений
|
||||
|
||||
@ -311,11 +311,11 @@ volumes:
|
||||
|
||||
```bash
|
||||
# Получение списка исключенных контейнеров
|
||||
curl -X GET "http://localhost:9001/api/excluded-containers" \
|
||||
curl -X GET "http://localhost:9001/api/containers/excluded" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Обновление списка
|
||||
curl -X POST "http://localhost:9001/api/excluded-containers" \
|
||||
curl -X POST "http://localhost:9001/api/containers/excluded" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '["container1", "container2"]'
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
LogBoard+ - это современная веб-панель для мониторинга и просмотра логов Docker контейнеров в реальном времени. Приложение идеально подходит для локальной разработки, позволяя разработчикам всегда держать логи микросервисов перед глазами на втором мониторе.
|
||||
|
||||
### 🎯 **Идеально для локальной разработки**
|
||||
### Идеально для локальной разработки
|
||||
|
||||
LogBoard+ особенно полезен для разработчиков, работающих с микросервисной архитектурой:
|
||||
|
||||
@ -16,7 +16,7 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
- **Мониторинг в реальном времени** - Видите проблемы сразу, как они возникают
|
||||
- **Централизованный просмотр** - Все логи в одном месте, а не в десятках терминалов
|
||||
|
||||
### 🐳 **Оптимизирован для Docker и Docker Compose**
|
||||
### Оптимизирован для Docker и Docker Compose
|
||||
|
||||
Если ваша инфраструктура основана на Docker и Docker Compose, LogBoard+ станет незаменимым инструментом:
|
||||
|
||||
@ -26,13 +26,13 @@ LogBoard+ особенно полезен для разработчиков, р
|
||||
- **Multi-view режим** - одновременный просмотр логов нескольких контейнеров
|
||||
- **Интеграция с Docker API** - прямая работа с контейнерами
|
||||
|
||||
### 🚀 **Производительность и удобство**
|
||||
### Производительность и удобство
|
||||
|
||||
Приложение предоставляет удобный веб-интерфейс для работы с логами микросервисов, поддерживает множественные проекты Docker Compose и включает в себя функции безопасности.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Установка
|
||||
### Вариант A: Разработка
|
||||
|
||||
```bash
|
||||
# Клонирование репозитория
|
||||
@ -40,17 +40,50 @@ git clone <repository-url>
|
||||
cd logboard
|
||||
|
||||
# Настройка переменных окружения
|
||||
cp env.example .env
|
||||
make setup
|
||||
|
||||
# Запуск приложения
|
||||
docker compose up --build -d
|
||||
make up
|
||||
```
|
||||
|
||||
### Доступ
|
||||
### Вариант B: Продакшен (пример docker-compose-prod.yaml)
|
||||
|
||||
- **URL:** http://localhost:9001
|
||||
- **Пользователь:** `admin`
|
||||
- **Пароль:** `admin` (обязательно измените в продакшене!)
|
||||
```yaml
|
||||
services:
|
||||
logboard:
|
||||
image: docker.io/inecs/logboard:v1
|
||||
container_name: logboard
|
||||
environment:
|
||||
LOGBOARD_PORT: "9001"
|
||||
LOGBOARD_TAIL: "500"
|
||||
LOGBOARD_USER: "admin"
|
||||
LOGBOARD_PASS: "admin"
|
||||
LOGBOARD_SNAPSHOT_DIR: "/app/snapshots"
|
||||
LOGBOARD_STATIC_DIR: "/app/static"
|
||||
LOGBOARD_INDEX_HTML: "./app/templates/index.html"
|
||||
TZ_TS: "Europe/Moscow"
|
||||
DOCKER_HOST: "unix:///var/run/docker.sock"
|
||||
SECRET_KEY: "your-secret-key-here"
|
||||
ENCRYPTION_KEY: "your-encryption-key-here"
|
||||
LOG_LEVEL: "INFO"
|
||||
LOG_FORMAT: "json"
|
||||
DEBUG_MODE: "false"
|
||||
AUTH_ENABLED: "true"
|
||||
SESSION_TIMEOUT: "3600"
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./snapshots:/app/snapshots
|
||||
restart: unless-stopped
|
||||
user: 0:0
|
||||
```
|
||||
|
||||
После запуска интерфейс доступен по адресу: http://localhost:9001
|
||||
|
||||
Учетные данные по умолчанию:
|
||||
- Пользователь: `admin`
|
||||
- Пароль: `admin` (обязательно измените в продакшене)
|
||||
|
||||
## Документация
|
||||
|
||||
@ -106,35 +139,35 @@ docker compose up --build -d
|
||||
|
||||
## Скриншоты
|
||||
|
||||
### 🔐 Страница входа
|
||||
### Страница входа
|
||||
| Светлая тема | Темная тема |
|
||||
|--------------|-------------|
|
||||
|  |  |
|
||||
|
||||
### 📊 Основной интерфейс
|
||||
### Основной интерфейс
|
||||
| Светлая тема | Темная тема |
|
||||
|--------------|-------------|
|
||||
|  |  |
|
||||
|
||||
### 🖥️ Multi-view режим
|
||||
### Multi-view режим
|
||||

|
||||
|
||||
### 📋 Карточки контейнеров
|
||||
### Карточки контейнеров
|
||||

|
||||
|
||||
### 📁 Проекты
|
||||
### Проекты
|
||||

|
||||
|
||||
### ⚙️ Настройки
|
||||
### Настройки
|
||||

|
||||
|
||||
### 🔧 Сворачиваемая боковая панель
|
||||
### Сворачиваемая боковая панель
|
||||

|
||||
|
||||
### ❓ Справка
|
||||
### Справка
|
||||

|
||||
|
||||
### 🚨 Страницы ошибок
|
||||
### Страницы ошибок
|
||||

|
||||
|
||||
## Архитектура
|
||||
@ -169,22 +202,22 @@ docker compose up --build -d
|
||||
- `GET /api/auth/me` - Информация о текущем пользователе
|
||||
|
||||
#### Контейнеры и сервисы
|
||||
- `GET /api/services` - Список контейнеров
|
||||
- `GET /api/projects` - Список проектов Docker Compose
|
||||
- `GET /api/containers/services` - Список контейнеров
|
||||
- `GET /api/containers/projects` - Список проектов Docker Compose
|
||||
- `GET /api/logs/{container_id}` - Логи контейнера
|
||||
- `GET /api/logs/stats/{container_id}` - Статистика логов
|
||||
|
||||
#### Управление
|
||||
- `GET /api/settings` - Настройки приложения
|
||||
- `GET /api/excluded-containers` - Список исключенных контейнеров
|
||||
- `POST /api/excluded-containers` - Обновление исключенных контейнеров
|
||||
- `POST /api/snapshot` - Создание снимка логов
|
||||
- `GET /api/containers/excluded` - Список исключенных контейнеров
|
||||
- `POST /api/containers/excluded` - Обновление исключенных контейнеров
|
||||
- `POST /api/logs/snapshot` - Создание снимка логов
|
||||
|
||||
### WebSocket API
|
||||
|
||||
- `ws://host:port/ws/logs/{container_id}` - Логи отдельного контейнера
|
||||
- `ws://host:port/ws/fan/{service_name}` - Логи сервиса (все реплики)
|
||||
- `ws://host:port/ws/fan_group` - Логи группы сервисов
|
||||
- `ws://host:port/api/websocket/logs/{container_id}` - Логи отдельного контейнера
|
||||
- `ws://host:port/api/websocket/fan/{service_name}` - Логи сервиса (все реплики)
|
||||
- `ws://host:port/api/websocket/fan_group` - Логи группы сервисов
|
||||
|
||||
## Конфигурация
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
## Обзор и применение
|
||||
|
||||
### 🎯 **Идеально для локальной разработки**
|
||||
### Идеально для локальной разработки
|
||||
|
||||
LogBoard+ создан специально для разработчиков, работающих с микросервисной архитектурой. Если вы используете Docker и Docker Compose для локальной разработки, этот инструмент станет незаменимым помощником:
|
||||
|
||||
@ -24,7 +24,7 @@ LogBoard+ создан специально для разработчиков,
|
||||
- **Мониторинг в реальном времени** - Видите проблемы сразу, как они возникают
|
||||
- **Централизованный просмотр** - Все логи в одном месте, а не в десятках терминалов
|
||||
|
||||
### 🐳 **Оптимизирован для Docker и Docker Compose**
|
||||
### Оптимизирован для Docker и Docker Compose
|
||||
|
||||
Если ваша инфраструктура основана на Docker и Docker Compose, LogBoard+ предоставляет:
|
||||
|
||||
@ -34,7 +34,7 @@ LogBoard+ создан специально для разработчиков,
|
||||
- **Multi-view режим** - одновременный просмотр логов нескольких контейнеров
|
||||
- **Интеграция с Docker API** - прямая работа с контейнерами
|
||||
|
||||
### 💡 **Сценарии использования**
|
||||
### Сценарии использования
|
||||
|
||||
- **Локальная разработка** - мониторинг логов микросервисов на втором мониторе
|
||||
- **Отладка проблем** - быстрый поиск ошибок в логах
|
||||
@ -212,11 +212,11 @@ curl -X POST "http://localhost:9001/api/auth/login" \
|
||||
|
||||
```bash
|
||||
# Проверка списка контейнеров
|
||||
curl -X GET "http://localhost:9001/api/services" \
|
||||
curl -X GET "http://localhost:9001/api/containers/services" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
||||
|
||||
# Проверка списка проектов
|
||||
curl -X GET "http://localhost:9001/api/projects" \
|
||||
curl -X GET "http://localhost:9001/api/containers/projects" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
||||
```
|
||||
|
||||
|
@ -129,39 +129,39 @@ echo ""
|
||||
|
||||
# Проверка наличия OpenSSL
|
||||
if ! command -v openssl &> /dev/null; then
|
||||
echo "❌ OpenSSL не найден. Установите OpenSSL для генерации ключей."
|
||||
echo "OpenSSL не найден. Установите OpenSSL для генерации ключей."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔐 Генерация SECRET_KEY..."
|
||||
echo "Генерация SECRET_KEY..."
|
||||
SECRET_KEY=$(openssl rand -hex 32)
|
||||
echo "SECRET_KEY=$SECRET_KEY"
|
||||
echo ""
|
||||
|
||||
echo "🔐 Генерация ENCRYPTION_KEY..."
|
||||
echo "Генерация ENCRYPTION_KEY..."
|
||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
echo "ENCRYPTION_KEY=$ENCRYPTION_KEY"
|
||||
echo ""
|
||||
|
||||
echo "🔐 Генерация пароля пользователя..."
|
||||
echo "Генерация пароля пользователя..."
|
||||
USER_PASSWORD=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-16)
|
||||
echo "LOGBOARD_PASS=$USER_PASSWORD"
|
||||
echo ""
|
||||
|
||||
echo "📝 Обновление .env файла..."
|
||||
echo "Обновление .env файла..."
|
||||
if [ -f .env ]; then
|
||||
# Создание резервной копии
|
||||
cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
|
||||
echo "✅ Создана резервная копия .env"
|
||||
echo "Создана резервная копия .env"
|
||||
|
||||
# Обновление ключей
|
||||
sed -i "s/^SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env
|
||||
sed -i "s/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY=$ENCRYPTION_KEY/" .env
|
||||
sed -i "s/^LOGBOARD_PASS=.*/LOGBOARD_PASS=$USER_PASSWORD/" .env
|
||||
|
||||
echo "✅ Ключи обновлены в .env"
|
||||
echo "Ключи обновлены в .env"
|
||||
else
|
||||
echo "⚠️ Файл .env не найден. Создайте его из env.example"
|
||||
echo "Файл .env не найден. Создайте его из env.example"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
@ -171,7 +171,7 @@ echo "2. Не передавайте ключи через незащищенн
|
||||
echo "3. Регулярно обновляйте ключи"
|
||||
echo "4. Используйте разные ключи для разных окружений"
|
||||
echo ""
|
||||
echo "✅ Генерация ключей завершена!"
|
||||
echo "Генерация ключей завершена!"
|
||||
```
|
||||
|
||||
```bash
|
||||
@ -213,9 +213,9 @@ def check_entropy(key_hex):
|
||||
print(f'Энтропия: {entropy:.2%}')
|
||||
|
||||
if entropy > 0.8:
|
||||
print('✅ Ключ имеет хорошую энтропию')
|
||||
print('Ключ имеет хорошую энтропию')
|
||||
else:
|
||||
print('⚠️ Ключ может иметь низкую энтропию')
|
||||
print('Ключ может иметь низкую энтропию')
|
||||
|
||||
# Проверка вашего ключа
|
||||
key = '8a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6'
|
||||
@ -660,43 +660,43 @@ echo ""
|
||||
|
||||
# Проверка паролей по умолчанию
|
||||
if grep -q "LOGBOARD_PASS=admin" .env; then
|
||||
echo "❌ Пароль по умолчанию не изменен!"
|
||||
echo "Пароль по умолчанию не изменен"
|
||||
else
|
||||
echo "✅ Пароль изменен"
|
||||
echo "Пароль изменен"
|
||||
fi
|
||||
|
||||
# Проверка секретных ключей
|
||||
if grep -q "SECRET_KEY=your-secret-key-here" .env; then
|
||||
echo "❌ SECRET_KEY не изменен!"
|
||||
echo "SECRET_KEY не изменен"
|
||||
else
|
||||
echo "✅ SECRET_KEY изменен"
|
||||
echo "SECRET_KEY изменен"
|
||||
fi
|
||||
|
||||
if grep -q "ENCRYPTION_KEY=your-encryption-key-here" .env; then
|
||||
echo "❌ ENCRYPTION_KEY не изменен!"
|
||||
echo "ENCRYPTION_KEY не изменен"
|
||||
else
|
||||
echo "✅ ENCRYPTION_KEY изменен"
|
||||
echo "ENCRYPTION_KEY изменен"
|
||||
fi
|
||||
|
||||
# Проверка HTTPS
|
||||
if curl -s -I https://localhost:9001 > /dev/null 2>&1; then
|
||||
echo "✅ HTTPS доступен"
|
||||
echo "HTTPS доступен"
|
||||
else
|
||||
echo "⚠️ HTTPS недоступен"
|
||||
echo "HTTPS недоступен"
|
||||
fi
|
||||
|
||||
# Проверка прав Docker
|
||||
if ls -la /var/run/docker.sock | grep -q "srw-rw-rw-"; then
|
||||
echo "✅ Docker socket доступен"
|
||||
echo "Docker socket доступен"
|
||||
else
|
||||
echo "❌ Проблемы с доступом к Docker socket"
|
||||
echo "Проблемы с доступом к Docker socket"
|
||||
fi
|
||||
|
||||
# Проверка файрвола
|
||||
if command -v ufw > /dev/null && sudo ufw status | grep -q "Status: active"; then
|
||||
echo "✅ Файрвол активен"
|
||||
echo "Файрвол активен"
|
||||
else
|
||||
echo "⚠️ Файрвол не настроен"
|
||||
echo "Файрвол не настроен"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
@ -363,7 +363,7 @@ echo $SESSION_TIMEOUT
|
||||
|
||||
```bash
|
||||
# Проверка WebSocket endpoint
|
||||
curl -I "http://localhost:9001/ws/logs/test"
|
||||
curl -I "http://localhost:9001/api/websocket/logs/test"
|
||||
|
||||
# Проверка логов приложения
|
||||
make logs | grep -i websocket
|
||||
@ -376,8 +376,8 @@ make logs | grep -i websocket
|
||||
|
||||
1. **Проблема с прокси**
|
||||
```nginx
|
||||
# Настройка Nginx для WebSocket
|
||||
location /ws/ {
|
||||
# Настройка Nginx для WebSocket с корректными путями API
|
||||
location /api/websocket/ {
|
||||
proxy_pass http://localhost:9001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
@ -390,7 +390,7 @@ make logs | grep -i websocket
|
||||
```javascript
|
||||
// Проверка токена в WebSocket URL
|
||||
const token = "your-valid-token";
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token}`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/logs/container-id?token=${token}`);
|
||||
```
|
||||
|
||||
3. **Проблема с CORS**
|
||||
@ -703,7 +703,7 @@ curl -X POST "http://localhost:9001/api/auth/login" \
|
||||
|
||||
# Проверка API с токеном
|
||||
TOKEN="your-token"
|
||||
curl -X GET "http://localhost:9001/api/services" \
|
||||
curl -X GET "http://localhost:9001/api/containers/services" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
@ -762,6 +762,6 @@ curl -s -X POST "http://localhost:9001/api/auth/login" \
|
||||
TOKEN=$(curl -s -X POST "http://localhost:9001/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin"}' | jq -r '.access_token')
|
||||
curl -s -X GET "http://localhost:9001/api/services" \
|
||||
curl -s -X GET "http://localhost:9001/api/containers/services" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq 'length'
|
||||
```
|
||||
|
@ -45,12 +45,12 @@ curl -X POST "http://localhost:9001/api/auth/login" \
|
||||
|
||||
```javascript
|
||||
const token = "your-jwt-token";
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token}`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/logs/container-id?token=${token}`);
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### ws://host:port/ws/logs/{container_id}
|
||||
### ws://host:port/api/websocket/logs/{container_id}
|
||||
|
||||
Получение логов отдельного контейнера в реальном времени.
|
||||
|
||||
@ -70,7 +70,7 @@ const ws = new WebSocket(`ws://localhost:9001/ws/logs/container-id?token=${token
|
||||
const containerId = "abc123def456";
|
||||
const token = "your-jwt-token";
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/logs/${containerId}?token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/logs/${containerId}?token=${token}&tail=100`);
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket соединение установлено');
|
||||
@ -104,7 +104,7 @@ Connected to container: myproject_web_1
|
||||
2024-01-15T10:30:15.123456789Z 2024/01/15 10:30:15 [notice] 1#1: start worker process 1234
|
||||
```
|
||||
|
||||
### ws://host:port/ws/fan/{service_name}
|
||||
### ws://host:port/api/websocket/fan/{service_name}
|
||||
|
||||
Получение логов всех реплик сервиса Docker Compose (fan-in).
|
||||
|
||||
@ -123,7 +123,7 @@ Connected to container: myproject_web_1
|
||||
const serviceName = "web";
|
||||
const token = "your-jwt-token";
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/fan/${serviceName}?token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/fan/${serviceName}?token=${token}&tail=100`);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
// Логи приходят с префиксом ID контейнера
|
||||
@ -140,7 +140,7 @@ ws.onmessage = function(event) {
|
||||
[def456gh] 2024-01-15T10:30:16.123456789Z 2024/01/15 10:30:16 [notice] 1#1: start worker processes
|
||||
```
|
||||
|
||||
### ws://host:port/ws/fan_group
|
||||
### ws://host:port/api/websocket/fan_group
|
||||
|
||||
Получение логов группы сервисов одновременно.
|
||||
|
||||
@ -159,7 +159,7 @@ ws.onmessage = function(event) {
|
||||
const services = "web,db,redis";
|
||||
const token = "your-jwt-token";
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:9001/ws/fan_group?services=${services}&token=${token}&tail=100`);
|
||||
const ws = new WebSocket(`ws://localhost:9001/api/websocket/fan_group?services=${services}&token=${token}&tail=100`);
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
// Логи приходят с префиксом ID контейнера и имени сервиса
|
||||
@ -193,7 +193,7 @@ class LogBoardWebSocket {
|
||||
connectToContainer(containerId, options = {}) {
|
||||
const { tail = 100, onMessage, onError, onClose } = options;
|
||||
|
||||
const url = `ws://${this.baseUrl.replace('http://', '')}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
||||
const url = `ws://${this.baseUrl.replace('http://', '')}/api/websocket/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = () => {
|
||||
@ -222,7 +222,7 @@ class LogBoardWebSocket {
|
||||
connectToService(serviceName, options = {}) {
|
||||
const { tail = 100, project, onMessage, onError, onClose } = options;
|
||||
|
||||
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
||||
let url = `ws://${this.baseUrl.replace('http://', '')}/api/websocket/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
||||
if (project) url += `&project=${project}`;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
@ -253,7 +253,7 @@ class LogBoardWebSocket {
|
||||
connectToServiceGroup(services, options = {}) {
|
||||
const { tail = 100, project, onMessage, onError, onClose } = options;
|
||||
|
||||
let url = `ws://${this.baseUrl.replace('http://', '')}/ws/fan_group?services=${services}&token=${this.token}&tail=${tail}`;
|
||||
let url = `ws://${this.baseUrl.replace('http://', '')}/api/websocket/fan_group?services=${services}&token=${this.token}&tail=${tail}`;
|
||||
if (project) url += `&project=${project}`;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
@ -393,7 +393,7 @@ class LogBoardWebSocket:
|
||||
|
||||
async def connect_to_container(self, container_id, tail=100, callback=None):
|
||||
"""Подключение к логам контейнера"""
|
||||
uri = f"{self.base_url}/ws/logs/{container_id}?token={self.token}&tail={tail}"
|
||||
uri = f"{self.base_url}/api/websocket/logs/{container_id}?token={self.token}&tail={tail}"
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
@ -412,7 +412,7 @@ class LogBoardWebSocket:
|
||||
|
||||
async def connect_to_service(self, service_name, tail=100, project=None, callback=None):
|
||||
"""Подключение к логам сервиса"""
|
||||
uri = f"{self.base_url}/ws/fan/{service_name}?token={self.token}&tail={tail}"
|
||||
uri = f"{self.base_url}/api/websocket/fan/{service_name}?token={self.token}&tail={tail}"
|
||||
if project:
|
||||
uri += f"&project={project}"
|
||||
|
||||
@ -433,7 +433,7 @@ class LogBoardWebSocket:
|
||||
|
||||
async def connect_to_service_group(self, services, tail=100, project=None, callback=None):
|
||||
"""Подключение к логам группы сервисов"""
|
||||
uri = f"{self.base_url}/ws/fan_group?services={services}&token={self.token}&tail={tail}"
|
||||
uri = f"{self.base_url}/api/websocket/fan_group?services={services}&token={self.token}&tail={tail}"
|
||||
if project:
|
||||
uri += f"&project={project}"
|
||||
|
||||
@ -507,7 +507,7 @@ class LogBoardWebSocket {
|
||||
connectToContainer(containerId, options = {}) {
|
||||
const { tail = 100, onMessage, onError, onClose } = options;
|
||||
|
||||
const url = `${this.baseUrl}/ws/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
||||
const url = `${this.baseUrl}/api/websocket/logs/${containerId}?token=${this.token}&tail=${tail}`;
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.on('open', () => {
|
||||
@ -538,7 +538,7 @@ class LogBoardWebSocket {
|
||||
connectToService(serviceName, options = {}) {
|
||||
const { tail = 100, project, onMessage, onError, onClose } = options;
|
||||
|
||||
let url = `${this.baseUrl}/ws/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
||||
let url = `${this.baseUrl}/api/websocket/fan/${serviceName}?token=${this.token}&tail=${tail}`;
|
||||
if (project) url += `&project=${project}`;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
|
113
release/docker-compose-prod.tmpl.yaml
Normal file
113
release/docker-compose-prod.tmpl.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
services:
|
||||
logboard:
|
||||
image: REGISTRY_PLACEHOLDER/IMAGE_NAME_PLACEHOLDER:IMAGE_TAG_PLACEHOLDER
|
||||
container_name: logboard
|
||||
environment:
|
||||
# ОСНОВНЫЕ НАСТРОЙКИ ПРИЛОЖЕНИЯ
|
||||
# Порт веб-интерфейса LogBoard+
|
||||
LOGBOARD_PORT: "${LOGBOARD_PORT}"
|
||||
# Количество строк логов по умолчанию (tail)
|
||||
LOGBOARD_TAIL: "${LOGBOARD_TAIL}"
|
||||
# Имя пользователя для входа
|
||||
LOGBOARD_USER: "${LOGBOARD_USER}"
|
||||
# Пароль для входа (обязательно поменять в продакшене)
|
||||
LOGBOARD_PASS: "${LOGBOARD_PASS}"
|
||||
# Директория для снимков логов (в контейнере)
|
||||
LOGBOARD_SNAPSHOT_DIR: "${LOGBOARD_SNAPSHOT_DIR}"
|
||||
# Директория для статических файлов
|
||||
LOGBOARD_STATIC_DIR: "${LOGBOARD_STATIC_DIR}"
|
||||
# Путь к HTML шаблону главной страницы
|
||||
LOGBOARD_INDEX_HTML: "${LOGBOARD_INDEX_HTML}"
|
||||
# Таймзона для временных меток (например: Europe/Moscow, UTC)
|
||||
TZ_TS: "${TZ_TS}"
|
||||
|
||||
# НАСТРОЙКИ DOCKER
|
||||
# Фильтр по проекту Docker Compose (опционально)
|
||||
COMPOSE_PROJECT_NAME: "${COMPOSE_PROJECT_NAME}"
|
||||
# Перечень проектов для отображения (через запятую)
|
||||
LOGBOARD_PROJECTS: "${LOGBOARD_PROJECTS}"
|
||||
# Путь к Docker socket / удалённому хосту
|
||||
DOCKER_HOST: "${DOCKER_HOST}"
|
||||
# Включить проверку TLS для Docker (удалённые хосты)
|
||||
DOCKER_TLS_VERIFY: "${DOCKER_TLS_VERIFY}"
|
||||
# Путь к TLS сертификатам Docker
|
||||
DOCKER_CERT_PATH: "${DOCKER_CERT_PATH}"
|
||||
# Внешние сети Docker (через запятую)
|
||||
DOCKER_NETWORKS: "${DOCKER_NETWORKS}"
|
||||
|
||||
# БЕЗОПАСНОСТЬ
|
||||
# Секретный ключ для JWT (обязательно поменять в продакшене)
|
||||
SECRET_KEY: "${SECRET_KEY}"
|
||||
# Ключ шифрования для чувствительных данных (обязательно поменять)
|
||||
ENCRYPTION_KEY: "${ENCRYPTION_KEY}"
|
||||
|
||||
# ЛОГИРОВАНИЕ
|
||||
# Уровень логирования (DEBUG, INFO, WARNING, ERROR)
|
||||
LOG_LEVEL: "${LOG_LEVEL}"
|
||||
# Формат логов (json, text)
|
||||
LOG_FORMAT: "${LOG_FORMAT}"
|
||||
|
||||
# ВЕБ-ИНТЕРФЕЙС
|
||||
# Заголовок веб-интерфейса
|
||||
WEB_TITLE: "${WEB_TITLE}"
|
||||
# Описание веб-интерфейса
|
||||
WEB_DESCRIPTION: "${WEB_DESCRIPTION}"
|
||||
# Версия веб-интерфейса
|
||||
WEB_VERSION: "${WEB_VERSION}"
|
||||
|
||||
# РЕЖИМ РАЗРАБОТКИ
|
||||
# Режим отладки (true/false)
|
||||
DEBUG_MODE: "${DEBUG_MODE}"
|
||||
|
||||
# ПРОИЗВОДИТЕЛЬНОСТЬ
|
||||
# Максимум одновременных подключений
|
||||
MAX_CONNECTIONS: "${MAX_CONNECTIONS}"
|
||||
# Таймаут подключения (сек)
|
||||
CONNECTION_TIMEOUT: "${CONNECTION_TIMEOUT}"
|
||||
# Таймаут чтения (сек)
|
||||
READ_TIMEOUT: "${READ_TIMEOUT}"
|
||||
|
||||
# ФИЛЬТРАЦИЯ КОНТЕЙНЕРОВ
|
||||
# Пропускать контейнеры с проблемным health check (true/false)
|
||||
LOGBOARD_SKIP_UNHEALTHY: "${LOGBOARD_SKIP_UNHEALTHY}"
|
||||
# Таймаут получения списка контейнеров (сек)
|
||||
LOGBOARD_CONTAINER_LIST_TIMEOUT: "${LOGBOARD_CONTAINER_LIST_TIMEOUT}"
|
||||
# Таймаут получения информации о контейнере (сек)
|
||||
LOGBOARD_CONTAINER_INFO_TIMEOUT: "${LOGBOARD_CONTAINER_INFO_TIMEOUT}"
|
||||
# Таймаут health check контейнера (сек)
|
||||
LOGBOARD_HEALTH_CHECK_TIMEOUT: "${LOGBOARD_HEALTH_CHECK_TIMEOUT}"
|
||||
|
||||
# АУТЕНТИФИКАЦИЯ
|
||||
# Включить аутентификацию (true/false)
|
||||
AUTH_ENABLED: "${AUTH_ENABLED}"
|
||||
# Метод аутентификации (jwt)
|
||||
AUTH_METHOD: "${AUTH_METHOD}"
|
||||
# Время жизни сессии (сек)
|
||||
SESSION_TIMEOUT: "${SESSION_TIMEOUT}"
|
||||
|
||||
# УВЕДОМЛЕНИЯ
|
||||
# Включить уведомления по email (true/false)
|
||||
NOTIFICATIONS_ENABLED: "${NOTIFICATIONS_ENABLED}"
|
||||
# SMTP сервер
|
||||
SMTP_HOST: "${SMTP_HOST}"
|
||||
# Порт SMTP
|
||||
SMTP_PORT: "${SMTP_PORT}"
|
||||
# Пользователь SMTP
|
||||
SMTP_USER: "${SMTP_USER}"
|
||||
# Пароль SMTP
|
||||
SMTP_PASS: "${SMTP_PASS}"
|
||||
# Email отправителя
|
||||
SMTP_FROM: "${SMTP_FROM}"
|
||||
|
||||
# AJAX ОБНОВЛЕНИЕ
|
||||
# Интервал AJAX обновления логов (мс)
|
||||
LOGBOARD_AJAX_UPDATE_INTERVAL: "${LOGBOARD_AJAX_UPDATE_INTERVAL}"
|
||||
|
||||
ports:
|
||||
- "${LOGBOARD_PORT}:${LOGBOARD_PORT}"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./snapshots:/app/snapshots
|
||||
restart: unless-stopped
|
||||
user: 0:0
|
||||
|
101
release/generate_compose.py
Normal file
101
release/generate_compose.py
Normal file
@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Генерация docker-compose-prod.yaml из шаблона и env.example.
|
||||
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
|
||||
Поведение:
|
||||
- Читает env.example и подставляет значения в docker-compose-prod.tmpl.yaml
|
||||
- Запрашивает (или берет из ENV/CLI) REGISTRY_HOST, IMAGE_NAME_FULL, IMAGE_TAG
|
||||
- Заменяет плейсхолдеры REGISTRY_PLACEHOLDER/IMAGE_NAME_PLACEHOLDER/IMAGE_TAG_PLACEHOLDER
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from typing import Dict
|
||||
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
|
||||
def parse_env_file(env_path: str) -> Dict[str, str]:
|
||||
variables: Dict[str, str] = {}
|
||||
with io.open(env_path, "r", encoding="utf-8") as fh:
|
||||
for raw in fh:
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, val = line.split("=", 1)
|
||||
key = key.strip()
|
||||
val = val.strip()
|
||||
if (val.startswith('"') and val.endswith('"')) or (
|
||||
val.startswith("'") and val.endswith("'")
|
||||
):
|
||||
val = val[1:-1]
|
||||
variables[key] = val
|
||||
return variables
|
||||
|
||||
|
||||
def substitute_placeholders(text: str, env: Dict[str, str]) -> str:
|
||||
pattern = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||
return pattern.sub(lambda m: env.get(m.group(1), m.group(0)), text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Генерация docker-compose-prod.yaml")
|
||||
parser.add_argument("--template", default=os.path.join(ROOT, "release", "docker-compose-prod.tmpl.yaml"))
|
||||
parser.add_argument("--output", default=os.path.join(ROOT, "docker-compose-prod.yaml"))
|
||||
parser.add_argument("--env", dest="env_path", default=os.path.join(ROOT, "env.example"))
|
||||
parser.add_argument("--registry", default=os.getenv("REGISTRY_HOST", ""))
|
||||
parser.add_argument("--image", dest="image", default=os.getenv("IMAGE_NAME_FULL", ""))
|
||||
parser.add_argument("--tag", dest="tag", default=os.getenv("IMAGE_TAG", ""))
|
||||
args = parser.parse_args()
|
||||
|
||||
env = parse_env_file(args.env_path)
|
||||
# Подстановка ${VAR} из env.example
|
||||
with io.open(args.template, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
content = substitute_placeholders(content, env)
|
||||
|
||||
# Комментирование строк, где остались неразрешенные ${VAR}
|
||||
# Ищем строки формата 'KEY: "${VAR}"' или 'KEY: ${VAR}' и комментируем их
|
||||
lines = content.splitlines()
|
||||
commented: list[str] = []
|
||||
unresolved_pattern = re.compile(r"^([ \t-]*[^:#\n]+:\s*)(\"?\$\{[A-Za-z_][A-Za-z0-9_]*\}\"?)\s*$")
|
||||
for line in lines:
|
||||
if unresolved_pattern.match(line):
|
||||
commented.append("# " + line)
|
||||
else:
|
||||
commented.append(line)
|
||||
content = "\n".join(commented) + ("\n" if content.endswith("\n") else "")
|
||||
|
||||
# Плейсхолдеры реестра/имени/тега
|
||||
registry = args.registry.strip() or input("Введите Docker Registry (например, ghcr.io или docker.io): ").strip() or "docker.io"
|
||||
if registry == "registry.hub.docker.com":
|
||||
registry = "docker.io"
|
||||
image = args.image.strip() or input(f"Введите имя образа (например, inecs/logboard) (по умолчанию: logboard): ").strip() or "logboard"
|
||||
tag = args.tag.strip() or input("Введите тег образа (по умолчанию: latest): ").strip() or "latest"
|
||||
|
||||
content = content.replace("REGISTRY_PLACEHOLDER", registry)
|
||||
content = content.replace("IMAGE_NAME_PLACEHOLDER", image)
|
||||
content = content.replace("IMAGE_TAG_PLACEHOLDER", tag)
|
||||
|
||||
with io.open(args.output, "w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
|
||||
print(f"Файл {os.path.relpath(args.output, ROOT)} сгенерирован")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
142
release/publish_image.py
Normal file
142
release/publish_image.py
Normal file
@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт публикации Docker-образа (multi-arch) в реестр.
|
||||
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
|
||||
Поведение:
|
||||
- Собирает multi-arch образ с использованием docker buildx
|
||||
- Логинится в реестр (пароль можно передать через аргумент/ENV, либо будет запрошен интерактивно)
|
||||
- Публикует образ
|
||||
|
||||
Параметры через аргументы/ENV:
|
||||
- --registry / $REGISTRY_HOST (например, docker.io, ghcr.io)
|
||||
- --image / $IMAGE_NAME_FULL (например, inecs/logboard)
|
||||
- --tag / $IMAGE_TAG (например, v1 или короткий sha)
|
||||
- --user / $REG_USER
|
||||
- --password / $REG_PASS
|
||||
- --platforms / $PLATFORMS (по умолчанию: linux/amd64,linux/arm64)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def run(cmd: List[str], check: bool = True, quiet: bool = False) -> subprocess.CompletedProcess:
|
||||
stdout = subprocess.DEVNULL if quiet else None
|
||||
stderr = subprocess.STDOUT if quiet else None
|
||||
return subprocess.run(cmd, check=check, stdout=stdout, stderr=stderr)
|
||||
|
||||
|
||||
def git_short_sha_default() -> str:
|
||||
try:
|
||||
out = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], stderr=subprocess.DEVNULL)
|
||||
return out.decode("utf-8").strip() or "latest"
|
||||
except Exception:
|
||||
return "latest"
|
||||
|
||||
|
||||
def ensure_buildx_builder(builder_name: str) -> None:
|
||||
try:
|
||||
run(["docker", "buildx", "inspect", builder_name], check=True, quiet=True)
|
||||
except subprocess.CalledProcessError:
|
||||
run(["docker", "buildx", "create", "--name", builder_name, "--use"], check=True)
|
||||
run(["docker", "buildx", "use", builder_name], check=True)
|
||||
|
||||
|
||||
def ensure_binfmt() -> None:
|
||||
try:
|
||||
run(["docker", "run", "--privileged", "--rm", "tonistiigi/binfmt", "--install", "all"], check=False, quiet=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Публикация Docker-образа в реестр (multi-arch)")
|
||||
parser.add_argument("--registry", default=os.getenv("REGISTRY_HOST", ""))
|
||||
parser.add_argument("--image", dest="image", default=os.getenv("IMAGE_NAME_FULL", ""))
|
||||
parser.add_argument("--tag", dest="tag", default=os.getenv("IMAGE_TAG", ""))
|
||||
parser.add_argument("--user", dest="user", default=os.getenv("REG_USER", ""))
|
||||
parser.add_argument("--password", dest="password", default=os.getenv("REG_PASS", ""))
|
||||
parser.add_argument("--platforms", default=os.getenv("PLATFORMS", "linux/amd64,linux/arm64"))
|
||||
parser.add_argument("--builder", default=os.getenv("BUILDX_BUILDER", "logboard_builder"))
|
||||
args = parser.parse_args()
|
||||
|
||||
# 1) Определяем реестр, поддерживая ввод вида "host" или "host/namespace"
|
||||
registry_input = (args.registry or os.getenv("REGISTRY_HOST", "")).strip()
|
||||
if not registry_input:
|
||||
registry_input = input(
|
||||
"Введите Docker Registry (например, ghcr.io, docker.io, registry.hub.docker.com/namespace): "
|
||||
).strip()
|
||||
registry_host = registry_input.split("/", 1)[0] if registry_input else "docker.io"
|
||||
registry_path = ""
|
||||
if "/" in registry_input:
|
||||
registry_path = registry_input.split("/", 1)[1]
|
||||
if not registry_host or registry_host == "registry.hub.docker.com":
|
||||
registry_host = "docker.io"
|
||||
|
||||
# 2) Имя образа. Если не задано, спрашиваем пользователя (по умолчанию logboard)
|
||||
image_input = (args.image or os.getenv("IMAGE_NAME_FULL", "")).strip()
|
||||
if not image_input:
|
||||
image_input = input(
|
||||
"Введите имя образа (по умолчанию: logboard): "
|
||||
).strip() or "logboard"
|
||||
# Если пользователь ввёл только имя без namespace, но registry_path задан, добавим префикс
|
||||
if "/" not in image_input and registry_path:
|
||||
image_full = f"{registry_path}/{image_input}"
|
||||
else:
|
||||
image_full = image_input
|
||||
|
||||
# 3) Тег: если не задан, спросим. По умолчанию короткий SHA
|
||||
tag_input = (args.tag or os.getenv("IMAGE_TAG", "")).strip()
|
||||
if not tag_input:
|
||||
default_tag = git_short_sha_default()
|
||||
tag_input = input(f"Введите тег образа (по умолчанию: {default_tag}): ").strip() or default_tag
|
||||
|
||||
user = (args.user or os.getenv("REG_USER", "")).strip()
|
||||
password = args.password
|
||||
if not user:
|
||||
user = input("Введите имя пользователя реестра: ").strip()
|
||||
if not password:
|
||||
password = getpass.getpass("Введите пароль реестра: ")
|
||||
|
||||
image_remote = f"{registry_host}/{image_full}:{tag_input}"
|
||||
|
||||
print(f"Логин в реестр: {registry_host}")
|
||||
p = subprocess.Popen(["docker", "login", registry_host, "-u", user, "--password-stdin"], stdin=subprocess.PIPE)
|
||||
p.communicate(input=password.encode("utf-8"))
|
||||
if p.returncode != 0:
|
||||
print("Ошибка логина в реестр", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("Установка binfmt (для multi-arch)...")
|
||||
ensure_binfmt()
|
||||
print(f"Подготовка buildx builder: {args.builder}")
|
||||
ensure_buildx_builder(args.builder)
|
||||
|
||||
print(f"Сборка и публикация образа: {image_remote}")
|
||||
run([
|
||||
"docker", "buildx", "build",
|
||||
"--platform", args.platforms,
|
||||
"-t", image_remote,
|
||||
"--push",
|
||||
".",
|
||||
], check=True)
|
||||
|
||||
print("Образ опубликован успешно!")
|
||||
print("Готово. Для генерации compose выполните: make release-compose")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user