Compare commits
22
Commits
v0.2.1
...
46cd7c39c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46cd7c39c0 | ||
|
|
6b44066843 | ||
|
|
ffc1026ce1 | ||
|
|
0680557886 | ||
|
|
ea453bd409 | ||
|
|
3c85936c73 | ||
|
|
6666fca194 | ||
|
|
9d03b0977d | ||
|
|
3a0c22abf0 | ||
|
|
ed48cacfbc | ||
|
|
3f9ecc1113 | ||
|
|
eb73c55ac2 | ||
|
|
8369b2a6ba | ||
|
|
d4a6726930 | ||
|
|
0d6e0fa565 | ||
|
|
3e78dcff0f | ||
|
|
291167e683 | ||
|
|
42f606610a | ||
|
|
dc8c6b0dc6 | ||
|
|
7f2ea4f3b4 | ||
|
|
923b1e7c57 | ||
|
|
ee263d6224 |
+38
@@ -0,0 +1,38 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build-test
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-test
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build-and-push
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-and-push
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
registry: gitea.jambotron.com
|
||||||
|
repo: gitea.jambotron.com/liosha84/htop-web
|
||||||
|
tags: ${DRONE_COMMIT_SHA},main-latest
|
||||||
|
username:
|
||||||
|
from_secret: REGISTRY_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
name: Docker Build and Push
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: gitea.jambotron.com
|
|
||||||
IMAGE_NAME: liosha84/htop-web
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Extract version from tag
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
# Remove 'v' prefix from tag (e.g., v0.1.0 → 0.1.0)
|
|
||||||
VERSION=$(echo "${{ gitea.ref }}" | sed 's|refs/tags/v||')
|
|
||||||
echo "version=${VERSION}" >> "${GITEA_OUTPUT}"
|
|
||||||
|
|
||||||
# Extract major.minor for minor tag (e.g., 0.1.0 → 0.1)
|
|
||||||
MINOR=$(echo "${VERSION}" | sed 's/\.[0-9]*$//')
|
|
||||||
echo "minor=${MINOR}" >> "${GITEA_OUTPUT}"
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Gitea Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.minor }}
|
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
|
||||||
Generated
+7
@@ -1,5 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel>
|
||||||
|
<module name="backend" target="21" />
|
||||||
|
<module name="backend.main" target="21" />
|
||||||
|
<module name="backend.test" target="21" />
|
||||||
|
</bytecodeTargetLevel>
|
||||||
|
</component>
|
||||||
<component name="JavacSettings">
|
<component name="JavacSettings">
|
||||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
<module name="backend" options="-parameters" />
|
<module name="backend" options="-parameters" />
|
||||||
|
|||||||
+8
-1
@@ -6,9 +6,16 @@ This project follows [Semantic Versioning](https://semver.org/). For more detail
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- **Automatic releases on merge** — Drone now builds and pushes Docker images automatically when a Pull Request is merged into `main`. No manual Git tags are needed. Each merge produces two tags: `main-{commit-sha}` for traceability and `main-latest` for the most recent stable build.
|
||||||
|
- **Git-flow workflow** — development happens in the `develop` branch. When code is ready for release, create a Pull Request from `develop` → `main`. Merging the PR triggers the automatic release.
|
||||||
|
|
||||||
|
## v0.3.0
|
||||||
|
|
||||||
|
- **CI/CD migrated to Drone** — replaced Gitea Actions with Drone CI/CD (`.drone.yml`). The Docker plugin `auto_tag` feature automatically creates Semantic Versioning tags (`0.3.0`, `0.3`) on Git tag pushes. Configure `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` secrets in the Gitea repository settings.
|
||||||
|
|
||||||
## v0.2.0
|
## v0.2.0
|
||||||
|
|
||||||
- **CI/CD with Gitea Actions** — automated Docker image build and push on Git tags. Each release produces three tags: full version (`0.2.0`), minor version (`0.2`), and `latest`. Configure `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` secrets in Gitea repository settings.
|
- **CI/CD with Drone** — automated Docker image build and push on Git tags using Drone CI/CD. Each release produces three tags: full version (`0.2.0`), minor version (`0.2`), and `latest`. Configure `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` secrets in Gitea repository settings.
|
||||||
- **Docker build optimization** — added `.dockerignore` to exclude unnecessary files from the build context, reducing build time and image size.
|
- **Docker build optimization** — added `.dockerignore` to exclude unnecessary files from the build context, reducing build time and image size.
|
||||||
- **Release documentation** — added detailed release process guide in `docs/DEVELOPMENT.md` with Semantic Versioning workflow.
|
- **Release documentation** — added detailed release process guide in `docs/DEVELOPMENT.md` with Semantic Versioning workflow.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ FROM eclipse-temurin:21-jre
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
|
COPY --from=builder /app/build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
|
||||||
|
|
||||||
RUN adduser --disabled-password --no-create-home appuser && \
|
RUN adduser --disabled-password --gecos "" appuser && \
|
||||||
chown appuser:appuser /app
|
chown appuser:appuser /app
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ def frontendDir = file('../frontend')
|
|||||||
def frontendDistDir = file('../frontend/dist/frontend/browser')
|
def frontendDistDir = file('../frontend/dist/frontend/browser')
|
||||||
def springStaticDir = file('src/main/resources/static')
|
def springStaticDir = file('src/main/resources/static')
|
||||||
|
|
||||||
|
// Skip frontend build tasks when building inside Docker (frontend already built in Stage 1)
|
||||||
|
def skipFrontend = project.hasProperty('skipFrontend') || System.getenv('SKIP_FRONTEND') != null
|
||||||
|
|
||||||
tasks.register('installFrontendDependencies', Exec) {
|
tasks.register('installFrontendDependencies', Exec) {
|
||||||
|
onlyIf { !skipFrontend }
|
||||||
workingDir frontendDir
|
workingDir frontendDir
|
||||||
|
|
||||||
inputs.file(file('../frontend/package.json'))
|
inputs.file(file('../frontend/package.json'))
|
||||||
@@ -50,6 +54,7 @@ tasks.register('installFrontendDependencies', Exec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('buildFrontend', Exec) {
|
tasks.register('buildFrontend', Exec) {
|
||||||
|
onlyIf { !skipFrontend }
|
||||||
dependsOn 'installFrontendDependencies'
|
dependsOn 'installFrontendDependencies'
|
||||||
workingDir frontendDir
|
workingDir frontendDir
|
||||||
|
|
||||||
@@ -66,9 +71,6 @@ tasks.register('buildFrontend', Exec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip frontend build tasks when building inside Docker (frontend already built in Stage 1)
|
|
||||||
def skipFrontend = project.hasProperty('skipFrontend') || System.getenv('SKIP_FRONTEND') != null
|
|
||||||
|
|
||||||
tasks.register('copyFrontend', Copy) {
|
tasks.register('copyFrontend', Copy) {
|
||||||
onlyIf { !skipFrontend }
|
onlyIf { !skipFrontend }
|
||||||
dependsOn 'buildFrontend'
|
dependsOn 'buildFrontend'
|
||||||
|
|||||||
+637
@@ -0,0 +1,637 @@
|
|||||||
|
# htop-web: Веб-интерфейс мониторинга системных ресурсов на базе Spring Boot WebFlux и Angular
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аннотация
|
||||||
|
|
||||||
|
В статье рассматривается архитектура и реализация **htop-web** — самохостимого веб-приложения для мониторинга системных ресурсов Linux-серверов в реальном времени. Приложение объединяет реактивный бэкенд на Spring Boot WebFlux с фронтендом на Angular 22, упаковывая оба компонента в единый Docker-контейнер. Ключевая особенность решения — сбор метрик хост-системы из контейнеризованной среды через монтирование файловых систем `/proc` и `/sys` хоста, что позволяет получить точные данные о CPU, памяти, дисках и сетевых интерфейсах реального сервера, а не виртуальной среды контейнера.
|
||||||
|
|
||||||
|
**Ключевые слова:** мониторинг, Spring Boot, WebFlux, Angular, Docker, контейнеризация, OSHI, системные метрики.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Введение
|
||||||
|
|
||||||
|
Мониторинг системных ресурсов является критически важной задачей при администрировании серверной инфраструктуры. Классические инструменты, такие как `htop`, `top`, `vmstat` и `iostat`, предоставляют детальную информацию, однако требуют доступа к терминалу и не подходят для удалённого мониторинга через веб-браузер.
|
||||||
|
|
||||||
|
Существующие веб-решения мониторинга (Grafana + Prometheus, Netdata, Zabbix) предлагают мощные возможности визуализации и агрегации, но часто требуют значительных ресурсов и сложной настройки. Для небольших инфраструктур и индивидуальных серверов возникает потребность в лёгком, самодостаточном решении, которое:
|
||||||
|
|
||||||
|
- Разворачивается за несколько команд;
|
||||||
|
- Не требует внешних баз данных или брокеров сообщений;
|
||||||
|
- Предоставляет информацию в реальном времени;
|
||||||
|
- Занимает минимальные ресурсы хоста.
|
||||||
|
|
||||||
|
**htop-web** отвечает этим требованиям, предоставляя компактный веб-дашборд, который собирает метрики напрямую из файловых систем Linux и отображает их через REST API и реактивный фронтенд.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Обзор существующих решений
|
||||||
|
|
||||||
|
| Решение | Тип | Ресурсы | Настройка | Особенности |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **htop / top** | CLI | Минимальные | Нет | Требует терминала, нет веб-интерфейса |
|
||||||
|
| **Grafana + Prometheus** | Веб | Высокие | Сложная | Агрегация, алерты, требует Node Exporter |
|
||||||
|
| **Netdata** | Веб | Средние | Средняя | Real-time, но тяжёлый агент |
|
||||||
|
| **Zabbix** | Веб | Высокие | Сложная | Корпоративный мониторинг, алертинг |
|
||||||
|
| **Glances** | Веб/CLI | Низкие | Простая | Python-зависимости, ограниченный UI |
|
||||||
|
| **htop-web** | Веб | Низкие | Простая | Один контейнер, нет внешних зависимостей |
|
||||||
|
|
||||||
|
htop-web занимает нишу между CLI-инструментами и тяжёлыми системами мониторинга, предлагая минималистичный подход с фокусом на простоту развёртывания.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Архитектура системы
|
||||||
|
|
||||||
|
### 3.1 Общая схема
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Веб-браузер │
|
||||||
|
│ (Angular 22, TypeScript) │
|
||||||
|
└──────────────────────┬──────────────────────────────┘
|
||||||
|
│ HTTP (polling, 1 сек)
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Docker-контейнер │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────┐ │
|
||||||
|
│ │ Spring Boot WebFlux (Java 21) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ REST API: /api/system/* │ │
|
||||||
|
│ │ ├─ /metrics — CPU, RAM, ОС │ │
|
||||||
|
│ │ ├─ /processes — список процессов │ │
|
||||||
|
│ │ ├─ /linux — uptime, load average │ │
|
||||||
|
│ │ ├─ /cpu — детали по ядрам │ │
|
||||||
|
│ │ ├─ /linux/memory — детальная память │ │
|
||||||
|
│ │ ├─ /linux/disks — диски и файловые сист. │ │
|
||||||
|
│ │ └─ /linux/network — сетевые интерфейсы │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Сервисы сбора метрик: │ │
|
||||||
|
│ │ ├─ SystemMetricsService │ │
|
||||||
|
│ │ ├─ SystemProcessesService │ │
|
||||||
|
│ │ ├─ LinuxDetailsService │ │
|
||||||
|
│ │ ├─ CpuDetailsService │ │
|
||||||
|
│ │ ├─ LinuxMemoryService │ │
|
||||||
|
│ │ ├─ LinuxDiskService │ │
|
||||||
|
│ │ └─ LinuxNetworkService │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Статические ресурсы Angular │ │
|
||||||
|
│ └─────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Монтированные тома хоста: │
|
||||||
|
│ /host/root ← / (корень хоста, read-only) │
|
||||||
|
│ /host/sys ← /sys (системные данные, read-only) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Ключевые принципы архитектуры
|
||||||
|
|
||||||
|
1. **Single-container deployment** — фронтенд и бэкенд упакованы в один JAR-файл и один Docker-контейнер.
|
||||||
|
2. **Host filesystem mounting** — метрики хоста получаются через монтирование `/proc` и `/sys` хоста в контейнер.
|
||||||
|
3. **Reactive backend** — Spring WebFlux обеспечивает неблокирующую обработку запросов.
|
||||||
|
4. **RxJS polling** — фронтенд использует реактивные потоки для периодического опроса API.
|
||||||
|
5. **Fallback mechanism** — при недоступности файлов хоста используется библиотека OSHI как запасной вариант.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Реализация бэкенда
|
||||||
|
|
||||||
|
### 4.1 Технологический стек
|
||||||
|
|
||||||
|
| Компонент | Технология | Версия |
|
||||||
|
|---|---|---|
|
||||||
|
| Язык | Java | 21 |
|
||||||
|
| Фреймворк | Spring Boot | 4.1.2 |
|
||||||
|
| Реактивность | Spring WebFlux | 4.1.2 |
|
||||||
|
| Системные метрики | OSHI | 7.7.2 |
|
||||||
|
| Сборка | Gradle | 8.x |
|
||||||
|
|
||||||
|
### 4.2 REST API контроллер
|
||||||
|
|
||||||
|
Контроллер `SystemMetricsController` экспонирует шесть REST-эндпоинтов, каждый из которых делегирует сбор данных соответствующему сервису:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/system")
|
||||||
|
public class SystemMetricsController {
|
||||||
|
|
||||||
|
private final SystemMetricsService _systemMetricsService;
|
||||||
|
private final LinuxDetailsService _linuxDetailsService;
|
||||||
|
private final CpuDetailsService _cpuDetailsService;
|
||||||
|
private final LinuxMemoryService _linuxMemoryService;
|
||||||
|
private final LinuxDiskService _linuxDiskService;
|
||||||
|
private final LinuxNetworkService _linuxNetworkService;
|
||||||
|
|
||||||
|
@GetMapping("/metrics")
|
||||||
|
public Mono<SystemMetricsResponse> getMetrics() {
|
||||||
|
return Mono.fromCallable(_systemMetricsService::getMetrics)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/linux")
|
||||||
|
public Mono<LinuxDetailsResponse> getLinuxDetails() {
|
||||||
|
return Mono.fromCallable(_linuxDetailsService::getDetails)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
}
|
||||||
|
// ... остальные эндпоинты
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый метод оборачивает синхронный вызов сервиса в `Mono` и выполняет его на `Schedulers.boundedElastic()`, что предотвращает блокировку реактивного пула потоков при чтении файловых систем.
|
||||||
|
|
||||||
|
### 4.3 Сбор метрик CPU и памяти
|
||||||
|
|
||||||
|
Сервис `SystemMetricsService` демонстрирует ключевой паттерн проекта — чтение метрик хоста из смонтированной файловой системы с fallback на OSHI:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class SystemMetricsService {
|
||||||
|
|
||||||
|
private static final Path HOST_ROOT = Path.of("/host/root");
|
||||||
|
private static final Path HOST_PROC = HOST_ROOT.resolve("proc");
|
||||||
|
|
||||||
|
public synchronized SystemMetricsResponse getMetrics() {
|
||||||
|
CpuTicks currentTicks = readHostCpuTicks();
|
||||||
|
double cpuUsage = 0.0;
|
||||||
|
|
||||||
|
if (currentTicks != null && _previousHostCpuTicks != null) {
|
||||||
|
long totalDelta = currentTicks.total() - _previousHostCpuTicks.total();
|
||||||
|
long idleDelta = currentTicks.idle() - _previousHostCpuTicks.idle();
|
||||||
|
|
||||||
|
if (totalDelta > 0 && idleDelta >= 0 && idleDelta <= totalDelta) {
|
||||||
|
cpuUsage = (double) (totalDelta - idleDelta) / totalDelta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... расчёт памяти и формирование ответа
|
||||||
|
}
|
||||||
|
|
||||||
|
private CpuTicks readHostCpuTicks() {
|
||||||
|
Path statFile = HOST_PROC.resolve("stat");
|
||||||
|
// Чтение /host/root/proc/stat и парсинг строки "cpu ..."
|
||||||
|
// Возвращает null при ошибке — триггер для fallback на OSHI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Алгоритм расчёта загрузки CPU основан на сравнении тиков процессора между двумя последовательными чтениями файла `/proc/stat`. Формула:
|
||||||
|
|
||||||
|
$$CPU_{usage} = \frac{\Delta_{total} - \Delta_{idle}}{\Delta_{total}}$$
|
||||||
|
|
||||||
|
где $\Delta_{total}$ — разница суммарных тиков всех состояний процессора, а $\Delta_{idle}$ — разница тиков в состоянии idle.
|
||||||
|
|
||||||
|
### 4.4 Сбор информации о процессах
|
||||||
|
|
||||||
|
Сервис `SystemProcessesService` парсит `/proc/[pid]/stat` для каждого процесса, вычисляя загрузку CPU на основе разницы тиков между двумя снимками:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class SystemProcessesService {
|
||||||
|
|
||||||
|
public synchronized List<ProcessInfoResponse> getProcesses() {
|
||||||
|
Path procRoot = Files.isDirectory(HOST_PROC) ? HOST_PROC : PROC;
|
||||||
|
|
||||||
|
long nowNanos = System.nanoTime();
|
||||||
|
double elapsedSeconds =
|
||||||
|
(nowNanos - _previousSampleNanos) / 1_000_000_000.0;
|
||||||
|
|
||||||
|
try (var entries = Files.list(procRoot)) {
|
||||||
|
entries.filter(path ->
|
||||||
|
path.getFileName().toString().matches("\\d+"))
|
||||||
|
.forEach(path -> {
|
||||||
|
int pid = Integer.parseInt(path.getFileName().toString());
|
||||||
|
ProcessSnapshot current = readProcess(path, pid, bootTimeSeconds);
|
||||||
|
// Расчёт CPU usage на основе delta тиков
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Fallback: getProcessesFromOshi() при недоступности /proc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Расчёт CPU usage процесса:
|
||||||
|
|
||||||
|
$$CPU_{process} = \frac{\Delta_{ticks}}{CLK\_TCK \times \Delta_{seconds}} \times \frac{100}{N_{cores}}$$
|
||||||
|
|
||||||
|
где $CLK\_TCK$ — количество тиков системы в секунду (обычно 100), а $N_{cores}$ — количество логических ядер.
|
||||||
|
|
||||||
|
### 4.5 Детали Linux-системы
|
||||||
|
|
||||||
|
Сервис `LinuxDetailsService` предоставляет информацию о системе через OSHI:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class LinuxDetailsService {
|
||||||
|
|
||||||
|
public LinuxDetailsResponse getDetails() {
|
||||||
|
String hostname = _operatingSystem.getNetworkParams().getHostName();
|
||||||
|
String distribution = _operatingSystem.getFamily();
|
||||||
|
String kernelVersion = _operatingSystem.getVersionInfo().getVersion();
|
||||||
|
long uptimeSeconds = _operatingSystem.getSystemUptime();
|
||||||
|
double[] loadAverages = _processor.getSystemLoadAverage(3);
|
||||||
|
|
||||||
|
return new LinuxDetailsResponse(
|
||||||
|
linux, hostname, distribution, kernelVersion,
|
||||||
|
uptimeSeconds,
|
||||||
|
safeLoad(loadAverages, 0), // 1 мин
|
||||||
|
safeLoad(loadAverages, 1), // 5 мин
|
||||||
|
safeLoad(loadAverages, 2), // 15 мин
|
||||||
|
_logicalProcessors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Реализация фронтенда
|
||||||
|
|
||||||
|
### 5.1 Технологический стек
|
||||||
|
|
||||||
|
| Компонент | Технология | Версия |
|
||||||
|
|---|---|---|
|
||||||
|
| Фреймворк | Angular | 22 |
|
||||||
|
| Язык | TypeScript | 5.x |
|
||||||
|
| Реактивность | RxJS | 7.x |
|
||||||
|
| Стили | SCSS | — |
|
||||||
|
|
||||||
|
### 5.2 Сервис опроса метрик
|
||||||
|
|
||||||
|
Фронтенд использует RxJS `timer` для периодического опроса API с интервалом 1 секуннда:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class SystemMetricsService {
|
||||||
|
private readonly http = inject(HttpClient);
|
||||||
|
private readonly refreshInterval = 1000;
|
||||||
|
|
||||||
|
readonly metrics$: Observable<SystemMetrics> = timer(0, this.refreshInterval).pipe(
|
||||||
|
switchMap(() =>
|
||||||
|
this.http.get<SystemMetrics>('/api/system/metrics').pipe(
|
||||||
|
catchError((error: unknown) => {
|
||||||
|
console.error('Failed to load system metrics', error);
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly processes$: Observable<SystemProcess[]> = timer(0, this.refreshInterval).pipe(
|
||||||
|
switchMap(() =>
|
||||||
|
this.http.get<SystemProcess[]>('/api/system/processes').pipe(
|
||||||
|
catchError((error: unknown) => of([] as SystemProcess[])),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
);
|
||||||
|
// ... остальные потоки: linux$, cpu$, memory$, disks$, network$
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Оператор `shareReplay` обеспечивает многоподписку: все компоненты, подписанные на один поток, получают одни и те же данные без дублирования HTTP-запросов.
|
||||||
|
|
||||||
|
### 5.3 Компонент дашборда
|
||||||
|
|
||||||
|
Компонент `Dashboard` инъецирует сервис метрик и предоставляет данные для шаблона:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
standalone: true,
|
||||||
|
imports: [AsyncPipe, DecimalPipe, JsonPipe],
|
||||||
|
templateUrl: './dashboard.html',
|
||||||
|
styleUrl: './dashboard.scss',
|
||||||
|
})
|
||||||
|
export class Dashboard {
|
||||||
|
private readonly _systemMetricsService = inject(SystemMetricsService);
|
||||||
|
|
||||||
|
readonly metrics$ = this._systemMetricsService.metrics$;
|
||||||
|
readonly processes$ = this._systemMetricsService.processes$;
|
||||||
|
readonly linux$ = this._systemMetricsService.linux$;
|
||||||
|
readonly cpu$ = this._systemMetricsService.cpu$;
|
||||||
|
readonly memory$ = this._systemMetricsService.memory$;
|
||||||
|
readonly disks$ = this._systemMetricsService.disks$;
|
||||||
|
readonly network$ = this._systemMetricsService.network$;
|
||||||
|
|
||||||
|
getSortedProcesses(processes: SystemProcess[]): SystemProcess[] {
|
||||||
|
return processes
|
||||||
|
.filter(p => p.name.toLowerCase().includes(this.processFilter))
|
||||||
|
.sort((a, b) => {
|
||||||
|
switch (this.processSort) {
|
||||||
|
case 'cpu': return a.cpuUsagePercent - b.cpuUsagePercent;
|
||||||
|
case 'memory': return a.residentMemoryBytes - b.residentMemoryBytes;
|
||||||
|
case 'name': return a.name.localeCompare(b.name);
|
||||||
|
case 'pid': return a.pid - b.pid;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Три режима отображения
|
||||||
|
|
||||||
|
Дашборд поддерживает три режима визуализации:
|
||||||
|
|
||||||
|
1. **Monitor** — карточный интерфейс с метриками CPU, памяти, дисков и сети.
|
||||||
|
2. **htop** — табличное представление процессов, стилизованное под классический htop.
|
||||||
|
3. **htop-original** — имитация терминального интерфейса htop с функциональными клавишами (F3 — поиск, F4 — фильтр, F6 — сортировка, F10 — выход).
|
||||||
|
|
||||||
|
```html
|
||||||
|
<nav class="view-switcher">
|
||||||
|
<button (click)="setViewMode('monitor')">Monitor</button>
|
||||||
|
<button (click)="setViewMode('htop')">htop</button>
|
||||||
|
<button (click)="setViewMode('htop-original')">htop-original</button>
|
||||||
|
</nav>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Сборка и упаковка
|
||||||
|
|
||||||
|
### 6.1 Gradle-сборка
|
||||||
|
|
||||||
|
Процесс сборки автоматически строит фронтенд и упаковывает его в бэкенд:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
tasks.register('buildFrontend', Exec) {
|
||||||
|
dependsOn 'installFrontendDependencies'
|
||||||
|
workingDir file('../frontend')
|
||||||
|
commandLine 'npm', 'run', 'build', '--', '--configuration', 'production'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyFrontend', Copy) {
|
||||||
|
dependsOn 'buildFrontend'
|
||||||
|
from(file('../frontend/dist/frontend/browser'))
|
||||||
|
into(file('src/main/resources/static'))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('processResources') {
|
||||||
|
dependsOn 'copyFrontend'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Порядок выполнения:
|
||||||
|
1. `npm ci` — установка зависимостей фронтенда;
|
||||||
|
2. `npm run build` — production-сборка Angular;
|
||||||
|
3. `copyFrontend` — копирование статических файлов в `src/main/resources/static`;
|
||||||
|
4. `bootJar` — упаковка JAR с фронтендом внутри.
|
||||||
|
|
||||||
|
### 6.2 Docker-образ
|
||||||
|
|
||||||
|
Dockerfile минимален и использует официальный образ Eclipse Temurin:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM eclipse-temurin:21-jre
|
||||||
|
WORKDIR /app
|
||||||
|
COPY backend/build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Образ весит около 300 МБ и не содержит инструментов разработки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Развёртывание через Docker Compose
|
||||||
|
|
||||||
|
### 7.1 Базовая конфигурация
|
||||||
|
|
||||||
|
Файл `docker-compose.yml` определяет сервис с доступом к файловой системе хоста:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
htop-web:
|
||||||
|
image: htop-web:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: htop-web
|
||||||
|
restart: unless-stopped
|
||||||
|
pid: host
|
||||||
|
volumes:
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /:/host/root:ro
|
||||||
|
ports:
|
||||||
|
- "8088:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ключевые параметры:**
|
||||||
|
|
||||||
|
| Параметр | Назначение |
|
||||||
|
|---|---|
|
||||||
|
| `pid: host` | Разделение PID-пространства хоста для видимости процессов |
|
||||||
|
| `/:/host/root:ro` | Чтение `/proc` хоста для метрик CPU, памяти и процессов |
|
||||||
|
| `/sys:/host/sys:ro` | Доступ к системным данным ядра |
|
||||||
|
| `restart: unless-stopped` | Автоматический перезапуск при сбое |
|
||||||
|
|
||||||
|
### 7.2 Пошаговая инструкция
|
||||||
|
|
||||||
|
#### Шаг 1: Клонирование репозитория
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.jambotron.com/liosha84/htop-web.git
|
||||||
|
cd htop-web
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Шаг 2: Сборка приложения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
./gradlew clean build
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
Gradle автоматически соберёт Angular-фронтенд и упакует его в JAR-файл бэкенда.
|
||||||
|
|
||||||
|
#### Шаг 3: Запуск контейнера
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение доступно по адресу `http://localhost:8088`.
|
||||||
|
|
||||||
|
#### Шаг 4: Проверка статуса
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Статус контейнера
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Логи в реальном времени
|
||||||
|
docker compose logs -f htop-web
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Шаг 5: Остановка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Продвинутое развёртывание с Caddy
|
||||||
|
|
||||||
|
Для продакшн-развёртывания рекомендуется использовать обратный прокси с автоматическим HTTPS:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
htop-web:
|
||||||
|
image: gitea.jambotron.com/liosha84/htop-web:latest
|
||||||
|
container_name: htop-web
|
||||||
|
restart: unless-stopped
|
||||||
|
pid: host
|
||||||
|
volumes:
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /:/host/root:ro
|
||||||
|
labels:
|
||||||
|
caddy: htop-web.example.com
|
||||||
|
caddy.reverse_proxy: "{{upstreams 8080}}"
|
||||||
|
networks:
|
||||||
|
- jambotron-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
jambotron-net:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Caddy автоматически выдаёт TLS-сертификат через Let's Encrypt и маршрутизирует запросы на контейнер.
|
||||||
|
|
||||||
|
### 7.4 Развёртывание на различных платформах
|
||||||
|
|
||||||
|
#### Linux-сервер
|
||||||
|
|
||||||
|
На Linux-сервере контейнер получает прямой доступ к `/proc` и `/sys` хоста, что обеспечивает точные метрики:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Desktop на Windows
|
||||||
|
|
||||||
|
Docker Desktop на Windows запускает Linux-контейнеры в виртуальной машине (WSL2 или Hyper-V). В этом случае метрики отражают состояние виртуальной машины, а не хостовой Windows-системы:
|
||||||
|
|
||||||
|
> **Важно:** Метрики из контейнера не представляют полную картину Windows-хоста. Для мониторинга Windows рекомендуется использовать нативные инструменты или развёртывание на Linux.
|
||||||
|
|
||||||
|
#### Raspberry Pi и ARM-устройства
|
||||||
|
|
||||||
|
htop-web поддерживает ARM-архитектуру благодаря кроссплатформенности Java и OSHI. Сборка выполняется аналогично:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Образ `eclipse-temurin:21-jre` доступен для `arm64` и `arm/v7`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Безопасность
|
||||||
|
|
||||||
|
Мониторинг системы раскрывает конфиденциальную информацию. При развёртывании необходимо учитывать:
|
||||||
|
|
||||||
|
1. **Аутентификация** — не размещайте htop-web в открытом доступе без защиты. Используйте обратный прокси с базовой аутентификацией или OAuth.
|
||||||
|
2. **HTTPS** — всегда используйте TLS при доступе из внешних сетей.
|
||||||
|
3. **Минимальные привилегии** — контейнер запускается с read-only доступом к файловой системе хоста, что снижает риски.
|
||||||
|
4. **Сетевая изоляция** — ограничьте доступ к порту 8088 через firewall или Docker-сети.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Сравнение с аналогами
|
||||||
|
|
||||||
|
### 9.1 Функциональное сравнение
|
||||||
|
|
||||||
|
| Функция | htop-web | Netdata | Glances | Grafana+Prometheus |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Веб-интерфейс | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| CPU-метрики | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Память | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Процессы | ✓ | ✓ | ✓ | ✗ |
|
||||||
|
| Диски | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Сеть | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| Развёртывание | 1 контейнер | 1 контейнер | pip install | 3+ компонента |
|
||||||
|
| Ресурсы | ~100 МБ RAM | ~200 МБ RAM | ~50 МБ RAM | ~500+ МБ RAM |
|
||||||
|
| История данных | ✗ | ✓ | ✗ | ✓ |
|
||||||
|
| Алертинг | ✗ | ✓ | ✓ | ✓ |
|
||||||
|
| Графики | Прогресс-бары | Графики | Прогресс-бары | Графики |
|
||||||
|
|
||||||
|
### 9.2 Позиционирование
|
||||||
|
|
||||||
|
htop-web ориентирован на сценарии, где важны:
|
||||||
|
|
||||||
|
- **Простота** — одна команда для запуска;
|
||||||
|
- **Минимальные ресурсы** — контейнер потребляет ~100 МБ RAM;
|
||||||
|
- **Отсутствие внешних зависимостей** — нет баз данных, брокеров, агентов;
|
||||||
|
- **Визуальное сходство с htop** — привычный интерфейс для системных администраторов.
|
||||||
|
|
||||||
|
Для задач, требующих исторических данных, алертинга и мультихостного мониторинга, рекомендуется использовать Grafana + Prometheus или Netdata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Заключение
|
||||||
|
|
||||||
|
htop-web демонстрирует, что эффективный инструмент мониторинга может быть реализован как компактное самодостаточное приложение. Ключевые достижения проекта:
|
||||||
|
|
||||||
|
1. **Единый контейнер** — Angular-фронтенд и Spring Boot-бэкенд упакованы в один JAR, что упрощает развёртывание и обновление.
|
||||||
|
2. **Точные метрики хоста** — монтирование `/proc` и `/sys` хоста позволяет контейнеризованному приложению получать реальные данные сервера, а не метрики виртуальной среды контейнера.
|
||||||
|
3. **Реактивная архитектура** — Spring WebFlux на бэкенде и RxJS на фронтенде обеспечивают эффективную обработку данных в реальном времени.
|
||||||
|
4. **Fallback-механизм** — библиотека OSHI служит запасным источником метрик при недоступности файлов хоста.
|
||||||
|
5. **Минимальные требования** — приложение работает на устройствах с 512 МБ RAM, включая Raspberry Pi.
|
||||||
|
|
||||||
|
Перспективы развития включают добавление исторического хранения метрик, алертинга по пороговым значениям и поддержки WebSocket для push-обновлений вместо polling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Список литературы
|
||||||
|
|
||||||
|
1. Spring Boot Documentation. — https://docs.spring.io/spring-boot/docs/current/reference/html/
|
||||||
|
2. Spring WebFlux Reactive Web. — https://docs.spring.io/spring-framework/reference/web/reactive.html
|
||||||
|
3. Angular Documentation. — https://angular.dev/
|
||||||
|
4. RxJS Documentation. — https://rxjs.dev/
|
||||||
|
5. OSHI — Operating System and Hardware Information. — https://github.com/oshi/oshi
|
||||||
|
6. Docker Documentation. — https://docs.docker.com/
|
||||||
|
7. Linux `/proc` filesystem. — man 5 proc
|
||||||
|
8. The htop source code. — https://github.com/htop-dev/htop
|
||||||
|
9. Netdata — Distributed real-time monitoring. — https://github.com/netdata/netdata
|
||||||
|
10. Grafana — The open observability platform. — https://github.com/grafana/grafana
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Скачивание и демо
|
||||||
|
|
||||||
|
### Живое демо
|
||||||
|
|
||||||
|
Попробовать htop-web в действии можно по адресу:
|
||||||
|
|
||||||
|
**https://htop-web.jambotron.com/**
|
||||||
|
|
||||||
|
Демо-инстанс развёрнут на Linux-сервере с доступом к реальным системным метрикам.
|
||||||
|
|
||||||
|
### Репозиторий
|
||||||
|
|
||||||
|
Исходный код проекта доступен на Gitea:
|
||||||
|
|
||||||
|
**https://gitea.jambotron.com/liosha84/htop-web**
|
||||||
|
|
||||||
|
Для клонирования:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.jambotron.com/liosha84/htop-web.git
|
||||||
|
cd htop-web
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker-образ
|
||||||
|
|
||||||
|
Готовый Docker-образ опубликован в контейнерном реестре Gitea:
|
||||||
|
|
||||||
|
**gitea.jambotron.com/liosha84/htop-web:latest**
|
||||||
|
|
||||||
|
Быстрый запуск одним командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name htop-web --pid host \
|
||||||
|
-v /:/host/root:ro -v /sys:/host/sys:ro \
|
||||||
|
-p 8088:8080 \
|
||||||
|
gitea.jambotron.com/liosha84/htop-web:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Статья подготовлена на основе исходного кода проекта htop-web, версия 0.0.1-SNAPSHOT.*
|
||||||
+59
-36
@@ -93,11 +93,26 @@ docker run -d \
|
|||||||
|
|
||||||
## Release process
|
## Release process
|
||||||
|
|
||||||
|
### Workflow overview
|
||||||
|
|
||||||
|
The project uses a **Git-flow** workflow with automatic releases:
|
||||||
|
|
||||||
|
1. All development happens in the **`develop`** branch.
|
||||||
|
2. When code is ready for release, create a **Pull Request** from `develop` → `main`.
|
||||||
|
3. **Merge the PR** into `main` — this triggers an automatic release.
|
||||||
|
4. **Drone** builds the Docker image and pushes it to the Gitea Container Registry.
|
||||||
|
|
||||||
|
**No manual Git tags are needed** — merging into `main` is the release.
|
||||||
|
|
||||||
|
```
|
||||||
|
develop → PR → merge into main → Drone builds → Docker image pushed
|
||||||
|
```
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Gitea Actions runner must be configured and running.
|
- Drone CI/CD runner must be configured and running at `drone.jambotron.com`.
|
||||||
- Gitea Container Registry must be enabled on the server.
|
- Gitea Container Registry must be enabled on the server.
|
||||||
- Repository secrets `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` must be set in the Gitea repository.
|
- Repository secrets `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` must be set in Drone.
|
||||||
|
|
||||||
### Enabling Container Registry on Gitea server
|
### Enabling Container Registry on Gitea server
|
||||||
|
|
||||||
@@ -134,6 +149,30 @@ sudo systemctl restart gitea
|
|||||||
|
|
||||||
**4. Verify** that the registry is working by visiting `https://gitea.jambotron.com/user/packages` — you should see a "Packages" tab.
|
**4. Verify** that the registry is working by visiting `https://gitea.jambotron.com/user/packages` — you should see a "Packages" tab.
|
||||||
|
|
||||||
|
### Configuring Drone CI/CD
|
||||||
|
|
||||||
|
The repository uses **Drone** for CI/CD. The configuration file `.drone.yml` is already included in the repository. Drone is hosted at a separate domain: `drone.jambotron.com`.
|
||||||
|
|
||||||
|
**1. Log in to Drone.**
|
||||||
|
|
||||||
|
Open `https://drone.jambotron.com` and sign in with your Gitea account.
|
||||||
|
|
||||||
|
**2. Activate the repository in Drone.**
|
||||||
|
|
||||||
|
After logging in, find `Liosha84/htop-web` in the repository list and click **Activate**.
|
||||||
|
|
||||||
|
**3. Configure Drone secrets.**
|
||||||
|
|
||||||
|
In Drone, open the repository and go to **Settings → Secrets**. Add two secrets:
|
||||||
|
|
||||||
|
**First secret:**
|
||||||
|
- **Name:** `REGISTRY_USERNAME`
|
||||||
|
- **Value:** `Liosha84` (your Gitea username)
|
||||||
|
|
||||||
|
**Second secret:**
|
||||||
|
- **Name:** `REGISTRY_PASSWORD`
|
||||||
|
- **Value:** paste the Access Token with `write:package` and `read:package` scopes.
|
||||||
|
|
||||||
### Creating an Access Token for CI/CD
|
### Creating an Access Token for CI/CD
|
||||||
|
|
||||||
The CI/CD workflow cannot use your account password. You need a Personal Access Token with package permissions:
|
The CI/CD workflow cannot use your account password. You need a Personal Access Token with package permissions:
|
||||||
@@ -150,66 +189,50 @@ The CI/CD workflow cannot use your account password. You need a Personal Access
|
|||||||
7. Click **Generate**.
|
7. Click **Generate**.
|
||||||
8. **Copy the token** — it will be shown only once! Save it securely.
|
8. **Copy the token** — it will be shown only once! Save it securely.
|
||||||
|
|
||||||
### Configuring repository secrets
|
|
||||||
|
|
||||||
1. Open the repository: `https://gitea.jambotron.com/Liosha84/htop-web`.
|
|
||||||
2. Go to **Settings** (tab at the top).
|
|
||||||
3. In the left menu: **Secrets** (or **Variables and Secrets → Secrets**).
|
|
||||||
4. Click **Add secret**.
|
|
||||||
|
|
||||||
**First secret:**
|
|
||||||
- **Name:** `REGISTRY_USERNAME`
|
|
||||||
- **Value:** `Liosha84` (your Gitea username)
|
|
||||||
|
|
||||||
**Second secret:**
|
|
||||||
- **Name:** `REGISTRY_PASSWORD`
|
|
||||||
- **Value:** paste the Access Token you created in the previous step.
|
|
||||||
|
|
||||||
### Creating a release
|
### Creating a release
|
||||||
|
|
||||||
1. **Update `CHANGELOG.md`** with changes for the new version.
|
1. **Update `CHANGELOG.md`** with changes for the new version in the `develop` branch.
|
||||||
|
|
||||||
2. **Commit and push** your changes:
|
2. **Commit and push** your changes to `develop`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git checkout develop
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "Prepare release v0.2.0"
|
git commit -m "Feature: new functionality"
|
||||||
git push origin master
|
git push origin develop
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Create and push a Git tag** with Semantic Versioning:
|
3. **Create a Pull Request** from `develop` → `main`:
|
||||||
|
|
||||||
```bash
|
Open `https://gitea.jambotron.com/jambotron_gang/htop-web/compare/develop...main` and click **Create Pull Request**.
|
||||||
git tag -a v0.2.0 -m "Release v0.2.0: new features and fixes"
|
|
||||||
git push origin v0.2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Gitea Actions** will automatically:
|
4. **Merge the PR** into `main`.
|
||||||
|
|
||||||
|
5. **Drone** will automatically:
|
||||||
- Build the Docker image using the multi-stage Dockerfile
|
- Build the Docker image using the multi-stage Dockerfile
|
||||||
- Tag it with the version (`0.2.0`), minor version (`0.2`), and `latest`
|
- Tag it with the short commit SHA (e.g., `abc1234`) and `main-latest`
|
||||||
- Push all three tags to the Gitea Container Registry
|
- Push both tags to the Gitea Container Registry
|
||||||
|
|
||||||
### Docker image tags
|
### Docker image tags
|
||||||
|
|
||||||
Each release produces three Docker image tags:
|
Each merge into `main` produces two Docker image tags:
|
||||||
|
|
||||||
| Tag | Example | Description |
|
| Tag | Example | Description |
|
||||||
|-----|---------|-------------|
|
|-----|---------|-------------|
|
||||||
| Full version | `gitea.jambotron.com/liosha84/htop-web:0.2.0` | Exact version, use in production |
|
| Commit SHA | `gitea.jambotron.com/liosha84/htop-web:abc1234` | Exact commit, use for reproducibility |
|
||||||
| Minor version | `gitea.jambotron.com/liosha84/htop-web:0.2` | Latest patch of 0.2.x |
|
| Main latest | `gitea.jambotron.com/liosha84/htop-web:main-latest` | Most recent merge into main |
|
||||||
| Latest | `gitea.jambotron.com/liosha84/htop-web:latest` | Most recent stable release |
|
|
||||||
|
|
||||||
### Using a specific version in production
|
### Using a specific version in production
|
||||||
|
|
||||||
In `docker-compose.yml`, always pin to a specific version:
|
In `docker-compose.yml`, pin to a specific commit SHA:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
htop-web:
|
htop-web:
|
||||||
image: gitea.jambotron.com/liosha84/htop-web:0.2.0
|
image: gitea.jambotron.com/liosha84/htop-web:abc1234
|
||||||
```
|
```
|
||||||
|
|
||||||
Avoid using `:latest` in production — it makes rollbacks impossible and breaks reproducibility.
|
Use `main-latest` for development/testing environments. For production, always use the commit SHA tag so you can roll back to a specific version.
|
||||||
|
|
||||||
## Contribution guidelines
|
## Contribution guidelines
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user