vault backup: 2025-08-04 15:28:34
This commit is contained in:
764
Идеи/Оптимизация ресурсов VPS/Hugo миграция и настройка.md
Normal file
764
Идеи/Оптимизация ресурсов VPS/Hugo миграция и настройка.md
Normal file
@@ -0,0 +1,764 @@
|
||||
# Hugo миграция и настройка
|
||||
|
||||
## 1. Миграция контента
|
||||
|
||||
### 1.1 Структура контента
|
||||
|
||||
**Текущая структура (Quartz):**
|
||||
```
|
||||
content/
|
||||
├── notes/
|
||||
│ ├── Идеи/
|
||||
│ ├── Мой сервер/
|
||||
│ └── index.md
|
||||
├── daily/
|
||||
└── templates/
|
||||
```
|
||||
|
||||
**Новая структура (Hugo):**
|
||||
```
|
||||
content/
|
||||
├── notes/
|
||||
│ ├── идеи/
|
||||
│ │ ├── obsidian-telegram-bot/
|
||||
│ │ │ ├── mvp-telegram-bota.md
|
||||
│ │ │ └── telegram-bot-dlya-obsidian.md
|
||||
│ │ └── pereezd-na-hugo/
|
||||
│ │ ├── plan-pereezda-na-hugo.md
|
||||
│ │ ├── webhook-server-na-go.md
|
||||
│ │ └── hugo-migratsiya-i-nastrojka.md
|
||||
│ ├── мой-сервер/
|
||||
│ │ ├── authelia-authentication/
|
||||
│ │ ├── git-service/
|
||||
│ │ ├── second-mind-setup/
|
||||
│ │ └── traefik-reverse-proxy/
|
||||
│ └── _index.md
|
||||
├── daily/
|
||||
│ └── _index.md
|
||||
└── templates/
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
### 1.2 Скрипт миграции
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# scripts/migrate_content.py
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
def slugify(text):
|
||||
"""Преобразование текста в slug для URL"""
|
||||
text = text.lower()
|
||||
text = re.sub(r'[^\w\s-]', '', text)
|
||||
text = re.sub(r'[-\s]+', '-', text)
|
||||
return text.strip('-')
|
||||
|
||||
def migrate_content():
|
||||
"""Миграция контента из Quartz в Hugo"""
|
||||
|
||||
# Пути
|
||||
quartz_content = "quartz/content"
|
||||
hugo_content = "hugo-site/content"
|
||||
|
||||
# Создание структуры Hugo
|
||||
os.makedirs(hugo_content, exist_ok=True)
|
||||
|
||||
# Обработка каждой директории
|
||||
for root, dirs, files in os.walk(quartz_content):
|
||||
# Пропускаем служебные директории
|
||||
if any(skip in root for skip in ['.obsidian', '.git', '__pycache__']):
|
||||
continue
|
||||
|
||||
# Определяем относительный путь
|
||||
rel_path = os.path.relpath(root, quartz_content)
|
||||
|
||||
# Создаем соответствующую структуру в Hugo
|
||||
hugo_path = os.path.join(hugo_content, rel_path)
|
||||
os.makedirs(hugo_path, exist_ok=True)
|
||||
|
||||
# Обрабатываем файлы
|
||||
for file in files:
|
||||
if file.endswith('.md'):
|
||||
migrate_markdown_file(
|
||||
os.path.join(root, file),
|
||||
os.path.join(hugo_path, file)
|
||||
)
|
||||
|
||||
def migrate_markdown_file(src_path, dst_path):
|
||||
"""Миграция отдельного Markdown файла"""
|
||||
|
||||
with open(src_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Обработка frontmatter
|
||||
content = process_frontmatter(content)
|
||||
|
||||
# Обработка внутренних ссылок
|
||||
content = process_internal_links(content)
|
||||
|
||||
# Обработка изображений
|
||||
content = process_images(content)
|
||||
|
||||
# Сохранение файла
|
||||
with open(dst_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
def process_frontmatter(content):
|
||||
"""Обработка YAML frontmatter для Hugo"""
|
||||
|
||||
# Проверяем наличие frontmatter
|
||||
if content.startswith('---'):
|
||||
# Извлекаем frontmatter
|
||||
parts = content.split('---', 2)
|
||||
if len(parts) >= 3:
|
||||
frontmatter = parts[1]
|
||||
body = parts[2]
|
||||
|
||||
# Добавляем Hugo-специфичные поля
|
||||
frontmatter = add_hugo_frontmatter(frontmatter)
|
||||
|
||||
return f"---\n{frontmatter}\n---\n{body}"
|
||||
|
||||
# Если frontmatter отсутствует, создаем базовый
|
||||
title = extract_title_from_content(content)
|
||||
return f"""---
|
||||
title: "{title}"
|
||||
date: {get_current_date()}
|
||||
draft: false
|
||||
---
|
||||
|
||||
{content}"""
|
||||
|
||||
def add_hugo_frontmatter(frontmatter):
|
||||
"""Добавление Hugo-специфичных полей в frontmatter"""
|
||||
|
||||
# Добавляем базовые поля если их нет
|
||||
if 'draft' not in frontmatter:
|
||||
frontmatter += '\ndraft: false'
|
||||
|
||||
if 'date' not in frontmatter:
|
||||
frontmatter += f'\ndate: {get_current_date()}'
|
||||
|
||||
return frontmatter
|
||||
|
||||
def process_internal_links(content):
|
||||
"""Обработка внутренних ссылок для Hugo"""
|
||||
|
||||
# Заменяем [[wiki links]] на Hugo ссылки
|
||||
content = re.sub(r'\[\[([^\]]+)\]\]', r'[{{< ref "\1" >}}]', content)
|
||||
|
||||
# Обрабатываем обычные ссылки
|
||||
content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', process_link, content)
|
||||
|
||||
return content
|
||||
|
||||
def process_link(match):
|
||||
"""Обработка ссылок"""
|
||||
text = match.group(1)
|
||||
url = match.group(2)
|
||||
|
||||
# Если это внутренняя ссылка на .md файл
|
||||
if url.endswith('.md'):
|
||||
# Преобразуем в Hugo ссылку
|
||||
slug = slugify(url.replace('.md', ''))
|
||||
return f'[{text}]({{{{< ref "{slug}" >}}}})'
|
||||
|
||||
return match.group(0)
|
||||
|
||||
def process_images(content):
|
||||
"""Обработка изображений"""
|
||||
|
||||
# Перемещаем изображения в static директорию
|
||||
# и обновляем ссылки
|
||||
content = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', process_image, content)
|
||||
|
||||
return content
|
||||
|
||||
def process_image(match):
|
||||
"""Обработка изображений"""
|
||||
alt = match.group(1)
|
||||
src = match.group(2)
|
||||
|
||||
# Если изображение локальное, перемещаем в static
|
||||
if not src.startswith(('http://', 'https://')):
|
||||
# Копируем изображение в static/images
|
||||
static_path = f"static/images/{os.path.basename(src)}"
|
||||
if os.path.exists(src):
|
||||
shutil.copy2(src, static_path)
|
||||
|
||||
# Обновляем ссылку
|
||||
src = f"/images/{os.path.basename(src)}"
|
||||
|
||||
return f''
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate_content()
|
||||
print("Миграция контента завершена!")
|
||||
```
|
||||
|
||||
## 2. Настройка Hugo
|
||||
|
||||
### 2.1 Базовая конфигурация
|
||||
|
||||
```toml
|
||||
# config.toml
|
||||
baseURL = "https://aepif.ru"
|
||||
languageCode = "ru-ru"
|
||||
title = "Second Mind"
|
||||
theme = "custom-theme"
|
||||
|
||||
[params]
|
||||
description = "Персональная база знаний и заметки"
|
||||
author = "AEP"
|
||||
github = "https://github.com/username"
|
||||
|
||||
# Настройки поиска
|
||||
enableSearch = true
|
||||
searchResultsLength = 10
|
||||
|
||||
# Настройки навигации
|
||||
showBreadcrumb = true
|
||||
showReadingTime = true
|
||||
showWordCount = true
|
||||
|
||||
[menu]
|
||||
[[menu.main]]
|
||||
identifier = "notes"
|
||||
name = "Заметки"
|
||||
url = "/notes/"
|
||||
weight = 1
|
||||
|
||||
[[menu.main]]
|
||||
identifier = "daily"
|
||||
name = "Дневник"
|
||||
url = "/daily/"
|
||||
weight = 2
|
||||
|
||||
[[menu.main]]
|
||||
identifier = "templates"
|
||||
name = "Шаблоны"
|
||||
url = "/templates/"
|
||||
weight = 3
|
||||
|
||||
[markup]
|
||||
[markup.goldmark]
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
[markup.highlight]
|
||||
style = "dracula"
|
||||
lineNos = true
|
||||
lineNumbersInTable = true
|
||||
|
||||
[outputs]
|
||||
home = ["HTML", "RSS", "JSON"]
|
||||
|
||||
[outputFormats]
|
||||
[outputFormats.JSON]
|
||||
mediaType = "application/json"
|
||||
baseName = "index"
|
||||
isPlainText = true
|
||||
notAlternative = true
|
||||
```
|
||||
|
||||
### 2.2 Структура темы
|
||||
|
||||
```
|
||||
themes/custom-theme/
|
||||
├── assets/
|
||||
│ ├── css/
|
||||
│ │ ├── main.scss
|
||||
│ │ └── _variables.scss
|
||||
│ ├── js/
|
||||
│ │ └── main.js
|
||||
│ └── images/
|
||||
├── layouts/
|
||||
│ ├── _default/
|
||||
│ │ ├── baseof.html
|
||||
│ │ ├── list.html
|
||||
│ │ └── single.html
|
||||
│ ├── partials/
|
||||
│ │ ├── head.html
|
||||
│ │ ├── header.html
|
||||
│ │ ├── footer.html
|
||||
│ │ ├── navigation.html
|
||||
│ │ ├── search.html
|
||||
│ │ └── breadcrumb.html
|
||||
│ ├── shortcodes/
|
||||
│ │ ├── note.html
|
||||
│ │ └── warning.html
|
||||
│ └── index.html
|
||||
├── static/
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── images/
|
||||
└── theme.toml
|
||||
```
|
||||
|
||||
### 2.3 Базовая тема
|
||||
|
||||
```html
|
||||
<!-- layouts/_default/baseof.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.LanguageCode }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>
|
||||
|
||||
{{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
||||
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
<body>
|
||||
{{ partial "header.html" . }}
|
||||
|
||||
<main class="container">
|
||||
{{ block "main" . }}{{ end }}
|
||||
</main>
|
||||
|
||||
{{ partial "footer.html" . }}
|
||||
|
||||
{{ $script := resources.Get "js/main.js" | resources.Minify }}
|
||||
<script src="{{ $script.RelPermalink }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- layouts/_default/single.html -->
|
||||
{{ define "main" }}
|
||||
<article class="note">
|
||||
<header class="note-header">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ if .Params.date }}
|
||||
<time datetime="{{ .Date.Format "2006-01-02" }}">
|
||||
{{ .Date.Format "2 January 2006" }}
|
||||
</time>
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{ partial "breadcrumb.html" . }}
|
||||
|
||||
<div class="note-content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
|
||||
{{ if .Params.tags }}
|
||||
<footer class="note-footer">
|
||||
<div class="tags">
|
||||
{{ range .Params.tags }}
|
||||
<a href="{{ "tags/" | relLangURL }}{{ . | urlize }}" class="tag">{{ . }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</footer>
|
||||
{{ end }}
|
||||
</article>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### 2.4 Стили
|
||||
|
||||
```scss
|
||||
// assets/css/main.scss
|
||||
@import "variables";
|
||||
|
||||
// Базовые стили
|
||||
body {
|
||||
font-family: $font-family-base;
|
||||
line-height: 1.6;
|
||||
color: $text-color;
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: $container-width;
|
||||
margin: 0 auto;
|
||||
padding: 0 $spacing;
|
||||
}
|
||||
|
||||
// Заметки
|
||||
.note {
|
||||
background: $note-bg;
|
||||
border-radius: $border-radius;
|
||||
padding: $spacing * 2;
|
||||
margin-bottom: $spacing * 2;
|
||||
box-shadow: $box-shadow;
|
||||
|
||||
&-header {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-bottom: $spacing;
|
||||
margin-bottom: $spacing * 2;
|
||||
|
||||
h1 {
|
||||
margin: 0 0 $spacing 0;
|
||||
color: $heading-color;
|
||||
}
|
||||
|
||||
time {
|
||||
color: $muted-color;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.7;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: $heading-color;
|
||||
margin-top: $spacing * 2;
|
||||
margin-bottom: $spacing;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: $spacing;
|
||||
}
|
||||
|
||||
code {
|
||||
background: $code-bg;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: $code-bg;
|
||||
padding: $spacing;
|
||||
border-radius: $border-radius;
|
||||
overflow-x: auto;
|
||||
|
||||
code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Навигация
|
||||
.navigation {
|
||||
background: $nav-bg;
|
||||
padding: $spacing;
|
||||
margin-bottom: $spacing * 2;
|
||||
border-radius: $border-radius;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: $spacing;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $nav-link-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: $nav-link-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Поиск
|
||||
.search {
|
||||
margin-bottom: $spacing * 2;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: $spacing;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $border-radius;
|
||||
font-size: 1em;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Хлебные крошки
|
||||
.breadcrumb {
|
||||
margin-bottom: $spacing;
|
||||
font-size: 0.9em;
|
||||
color: $muted-color;
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Оптимизации
|
||||
|
||||
### 3.1 Производительность
|
||||
|
||||
```toml
|
||||
# config.toml (дополнительные настройки)
|
||||
[build]
|
||||
writeStats = true
|
||||
|
||||
[imaging]
|
||||
quality = 85
|
||||
resampleFilter = "Lanczos"
|
||||
|
||||
[minify]
|
||||
[minify.tdewolff]
|
||||
[minify.tdewolff.css]
|
||||
keepCSS2 = true
|
||||
precision = 0
|
||||
[minify.tdewolff.html]
|
||||
keepConditionalComments = true
|
||||
keepDefaultDoctype = true
|
||||
keepDocumentTags = true
|
||||
keepEndTags = true
|
||||
keepQuotes = false
|
||||
keepWhitespace = false
|
||||
[minify.tdewolff.js]
|
||||
keepVarNames = false
|
||||
precision = 0
|
||||
[minify.tdewolff.json]
|
||||
keepNumbers = false
|
||||
precision = 0
|
||||
[minify.tdewolff.svg]
|
||||
keepComments = false
|
||||
[minify.tdewolff.xml]
|
||||
keepWhitespace = false
|
||||
```
|
||||
|
||||
### 3.2 SEO оптимизация
|
||||
|
||||
```html
|
||||
<!-- layouts/partials/head.html -->
|
||||
{{ if .IsHome }}
|
||||
<meta name="description" content="{{ .Site.Params.description }}">
|
||||
{{ else }}
|
||||
<meta name="description" content="{{ .Summary }}">
|
||||
{{ end }}
|
||||
|
||||
<meta name="author" content="{{ .Site.Params.author }}">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }}{{ end }}">
|
||||
<meta property="og:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary }}{{ end }}">
|
||||
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}">
|
||||
<meta property="og:url" content="{{ .Permalink }}">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }}{{ end }}">
|
||||
<meta name="twitter:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Summary }}{{ end }}">
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
```
|
||||
|
||||
### 3.3 Поиск
|
||||
|
||||
```javascript
|
||||
// assets/js/search.js
|
||||
class Search {
|
||||
constructor() {
|
||||
this.searchIndex = null;
|
||||
this.searchInput = document.getElementById('search-input');
|
||||
this.searchResults = document.getElementById('search-results');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.loadSearchIndex();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
async loadSearchIndex() {
|
||||
try {
|
||||
const response = await fetch('/index.json');
|
||||
this.searchIndex = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Failed to load search index:', error);
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.searchInput.addEventListener('input', (e) => {
|
||||
this.search(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
search(query) {
|
||||
if (!this.searchIndex || !query.trim()) {
|
||||
this.hideResults();
|
||||
return;
|
||||
}
|
||||
|
||||
const results = this.searchIndex.filter(item => {
|
||||
const searchText = `${item.title} ${item.content}`.toLowerCase();
|
||||
return searchText.includes(query.toLowerCase());
|
||||
});
|
||||
|
||||
this.displayResults(results.slice(0, 10));
|
||||
}
|
||||
|
||||
displayResults(results) {
|
||||
if (results.length === 0) {
|
||||
this.searchResults.innerHTML = '<p>Ничего не найдено</p>';
|
||||
} else {
|
||||
const html = results.map(item => `
|
||||
<div class="search-result">
|
||||
<h3><a href="${item.url}">${item.title}</a></h3>
|
||||
<p>${item.content.substring(0, 150)}...</p>
|
||||
</div>
|
||||
`).join('');
|
||||
this.searchResults.innerHTML = html;
|
||||
}
|
||||
|
||||
this.showResults();
|
||||
}
|
||||
|
||||
showResults() {
|
||||
this.searchResults.style.display = 'block';
|
||||
}
|
||||
|
||||
hideResults() {
|
||||
this.searchResults.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация поиска
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new Search();
|
||||
});
|
||||
```
|
||||
|
||||
## 4. Тестирование
|
||||
|
||||
### 4.1 Локальная разработка
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/dev.sh
|
||||
|
||||
echo "Запуск Hugo в режиме разработки..."
|
||||
|
||||
# Установка зависимостей
|
||||
hugo mod get
|
||||
|
||||
# Запуск сервера разработки
|
||||
hugo server \
|
||||
--bind 0.0.0.0 \
|
||||
--port 1313 \
|
||||
--disableFastRender \
|
||||
--noHTTPCache \
|
||||
--buildDrafts \
|
||||
--buildFuture
|
||||
```
|
||||
|
||||
### 4.2 Тестирование сборки
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/test-build.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Тестирование сборки Hugo..."
|
||||
|
||||
# Очистка предыдущей сборки
|
||||
rm -rf public/
|
||||
|
||||
# Сборка сайта
|
||||
hugo --minify
|
||||
|
||||
# Проверка размера
|
||||
echo "Размер сборки:"
|
||||
du -sh public/
|
||||
|
||||
# Проверка количества страниц
|
||||
echo "Количество страниц:"
|
||||
find public/ -name "*.html" | wc -l
|
||||
|
||||
echo "Сборка завершена успешно!"
|
||||
```
|
||||
|
||||
## 5. Развертывание
|
||||
|
||||
### 5.1 Скрипт сборки для production
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/build-production.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Сборка Hugo для production..."
|
||||
|
||||
# Очистка
|
||||
rm -rf public/
|
||||
|
||||
# Сборка с оптимизациями
|
||||
hugo \
|
||||
--minify \
|
||||
--gc \
|
||||
--cleanDestinationDir \
|
||||
--environment production
|
||||
|
||||
# Оптимизация изображений
|
||||
find public/ -name "*.jpg" -o -name "*.png" | xargs -I {} convert {} -strip -quality 85 {}
|
||||
|
||||
# Сжатие статических файлов
|
||||
find public/ -name "*.css" -o -name "*.js" | xargs gzip -9
|
||||
|
||||
echo "Production сборка завершена!"
|
||||
```
|
||||
|
||||
### 5.2 Nginx конфигурация
|
||||
|
||||
```nginx
|
||||
# configs/nginx.conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name aepif.ru www.aepif.ru;
|
||||
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
|
||||
# Gzip сжатие
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||
|
||||
# Кэширование статических файлов
|
||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Обработка Hugo страниц
|
||||
location / {
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
}
|
||||
|
||||
# Обработка 404 ошибок
|
||||
error_page 404 /404.html;
|
||||
|
||||
# Безопасность
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
}
|
||||
```
|
||||
549
Идеи/Оптимизация ресурсов VPS/Webhook Server на Go.md
Normal file
549
Идеи/Оптимизация ресурсов VPS/Webhook Server на Go.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# Webhook Server на Go
|
||||
|
||||
## 1. Обзор проекта
|
||||
|
||||
### 1.1 Назначение
|
||||
Webhook сервер на Go для автоматической сборки и деплоя Hugo сайта при изменениях в Git репозитории.
|
||||
|
||||
### 1.2 Ключевые функции
|
||||
- Обработка webhook от Git (GitHub, GitLab, Gitea)
|
||||
- Автоматическое клонирование/обновление репозитория
|
||||
- Запуск Hugo сборки
|
||||
- Валидация и безопасность
|
||||
- Логирование и мониторинг
|
||||
- Обработка ошибок и retry логика
|
||||
|
||||
## 2. Архитектура
|
||||
|
||||
### 2.1 Компонентная диаграмма
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Git Webhook] --> B[Webhook Handler]
|
||||
B --> C[Validator]
|
||||
C --> D[Git Client]
|
||||
D --> E[Repository Manager]
|
||||
E --> F[Hugo Builder]
|
||||
F --> G[File System]
|
||||
G --> H[Nginx Reload]
|
||||
|
||||
subgraph "Webhook Server"
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
A
|
||||
G
|
||||
H
|
||||
end
|
||||
```
|
||||
|
||||
### 2.2 Структура проекта
|
||||
|
||||
```
|
||||
webhook-server/
|
||||
├── cmd/
|
||||
│ └── server/
|
||||
│ └── main.go # Точка входа приложения
|
||||
├── internal/
|
||||
│ ├── handler/
|
||||
│ │ ├── webhook.go # Обработчик webhook
|
||||
│ │ └── health.go # Health check endpoint
|
||||
│ ├── builder/
|
||||
│ │ ├── hugo.go # Hugo сборка
|
||||
│ │ └── git.go # Git операции
|
||||
│ ├── config/
|
||||
│ │ └── config.go # Конфигурация
|
||||
│ ├── validator/
|
||||
│ │ └── webhook.go # Валидация webhook
|
||||
│ └── logger/
|
||||
│ └── logger.go # Структурированное логирование
|
||||
├── pkg/
|
||||
│ ├── git/
|
||||
│ │ └── client.go # Git клиент
|
||||
│ └── metrics/
|
||||
│ └── prometheus.go # Метрики Prometheus
|
||||
├── configs/
|
||||
│ ├── config.yaml # Конфигурация
|
||||
│ └── nginx.conf # Nginx конфигурация
|
||||
├── scripts/
|
||||
│ ├── build.sh # Скрипт сборки
|
||||
│ └── deploy.sh # Скрипт деплоя
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 3. Техническая реализация
|
||||
|
||||
### 3.1 Основные зависимости
|
||||
|
||||
```go
|
||||
// go.mod
|
||||
module webhook-server
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-git/go-git/v5 v5.8.1
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/viper v1.17.0
|
||||
gopkg.in/go-playground/webhooks.v5 v5.17.0
|
||||
)
|
||||
```
|
||||
|
||||
### 3.2 Конфигурация
|
||||
|
||||
```yaml
|
||||
# configs/config.yaml
|
||||
server:
|
||||
port: 8080
|
||||
host: "0.0.0.0"
|
||||
|
||||
git:
|
||||
repository: "https://github.com/user/second-mind"
|
||||
branch: "main"
|
||||
webhook_secret: "your-webhook-secret"
|
||||
ssh_key_path: "/root/.ssh/id_rsa"
|
||||
|
||||
hugo:
|
||||
source_path: "/app/hugo-site"
|
||||
output_path: "/var/www/html"
|
||||
config_file: "config.toml"
|
||||
base_url: "https://aepif.ru"
|
||||
|
||||
nginx:
|
||||
config_path: "/etc/nginx/nginx.conf"
|
||||
reload_command: "nginx -s reload"
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
format: "json"
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
port: 9090
|
||||
```
|
||||
|
||||
### 3.3 Основные компоненты
|
||||
|
||||
#### Webhook Handler
|
||||
```go
|
||||
// internal/handler/webhook.go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type WebhookHandler struct {
|
||||
gitClient *git.Client
|
||||
hugoBuilder *builder.HugoBuilder
|
||||
validator *validator.WebhookValidator
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) HandleWebhook(c *gin.Context) {
|
||||
// Валидация webhook
|
||||
if err := h.validator.Validate(c.Request); err != nil {
|
||||
h.logger.WithError(err).Error("Webhook validation failed")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Асинхронная обработка
|
||||
go h.processWebhook()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "processing"})
|
||||
}
|
||||
|
||||
func (h *WebhookHandler) processWebhook() {
|
||||
// Клонирование/обновление репозитория
|
||||
if err := h.gitClient.Pull(); err != nil {
|
||||
h.logger.WithError(err).Error("Git pull failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Hugo сборка
|
||||
if err := h.hugoBuilder.Build(); err != nil {
|
||||
h.logger.WithError(err).Error("Hugo build failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Перезагрузка Nginx
|
||||
if err := h.reloadNginx(); err != nil {
|
||||
h.logger.WithError(err).Error("Nginx reload failed")
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Deployment completed successfully")
|
||||
}
|
||||
```
|
||||
|
||||
#### Hugo Builder
|
||||
```go
|
||||
// internal/builder/hugo.go
|
||||
package builder
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type HugoBuilder struct {
|
||||
sourcePath string
|
||||
outputPath string
|
||||
configFile string
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (h *HugoBuilder) Build() error {
|
||||
h.logger.Info("Starting Hugo build")
|
||||
|
||||
cmd := exec.Command("hugo",
|
||||
"--source", h.sourcePath,
|
||||
"--destination", h.outputPath,
|
||||
"--config", filepath.Join(h.sourcePath, h.configFile),
|
||||
"--minify",
|
||||
"--cleanDestinationDir",
|
||||
)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
h.logger.WithError(err).WithField("output", string(output)).Error("Hugo build failed")
|
||||
return err
|
||||
}
|
||||
|
||||
h.logger.WithField("output", string(output)).Info("Hugo build completed")
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Git Client
|
||||
```go
|
||||
// pkg/git/client.go
|
||||
package git
|
||||
|
||||
import (
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
repositoryPath string
|
||||
remoteURL string
|
||||
branch string
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (c *Client) Pull() error {
|
||||
c.logger.Info("Starting git pull")
|
||||
|
||||
repo, err := git.PlainOpen(c.repositoryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = worktree.Pull(&git.PullOptions{
|
||||
RemoteName: "origin",
|
||||
BranchName: plumbing.NewBranchReferenceName(c.branch),
|
||||
})
|
||||
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
c.logger.WithError(err).Error("Git pull failed")
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Info("Git pull completed")
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Метрики и мониторинг
|
||||
|
||||
```go
|
||||
// pkg/metrics/prometheus.go
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
BuildDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||
Name: "hugo_build_duration_seconds",
|
||||
Help: "Duration of Hugo build in seconds",
|
||||
})
|
||||
|
||||
BuildSuccess = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "hugo_build_success_total",
|
||||
Help: "Total number of successful builds",
|
||||
})
|
||||
|
||||
BuildFailures = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "hugo_build_failures_total",
|
||||
Help: "Total number of failed builds",
|
||||
})
|
||||
|
||||
WebhookRequests = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "webhook_requests_total",
|
||||
Help: "Total number of webhook requests",
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
## 4. Docker контейнеризация
|
||||
|
||||
### 4.1 Dockerfile
|
||||
|
||||
```dockerfile
|
||||
# Многоэтапная сборка
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
# Установка зависимостей
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование go.mod и go.sum
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Сборка приложения
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
|
||||
|
||||
# Финальный образ
|
||||
FROM alpine:latest
|
||||
|
||||
# Установка необходимых пакетов
|
||||
RUN apk --no-cache add ca-certificates tzdata hugo git openssh-client nginx
|
||||
|
||||
# Создание пользователя
|
||||
RUN addgroup -g 1000 -S appgroup && \
|
||||
adduser -u 1000 -S appuser -G appgroup
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование бинарника
|
||||
COPY --from=builder /app/main .
|
||||
|
||||
# Копирование конфигурации
|
||||
COPY configs/ /app/configs/
|
||||
|
||||
# Создание директорий
|
||||
RUN mkdir -p /var/www/html /root/.ssh && \
|
||||
chown -R appuser:appgroup /app /var/www/html
|
||||
|
||||
# Переключение на пользователя
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8080 9090
|
||||
|
||||
CMD ["./main"]
|
||||
```
|
||||
|
||||
### 4.2 Docker Compose
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
webhook-server:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./configs:/app/configs
|
||||
- /var/www/html:/var/www/html
|
||||
- ~/.ssh:/root/.ssh:ro
|
||||
environment:
|
||||
- CONFIG_PATH=/app/configs/config.yaml
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- webhook-network
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /var/www/html:/usr/share/nginx/html
|
||||
- ./configs/nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- webhook-server
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- webhook-network
|
||||
|
||||
networks:
|
||||
webhook-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## 5. Безопасность
|
||||
|
||||
### 5.1 Webhook валидация
|
||||
- Проверка подписи webhook
|
||||
- Валидация payload
|
||||
- Rate limiting
|
||||
- IP whitelist
|
||||
|
||||
### 5.2 Git безопасность
|
||||
- SSH ключи для аутентификации
|
||||
- Проверка подписи коммитов
|
||||
- Ограничение доступа к репозиторию
|
||||
|
||||
### 5.3 Системная безопасность
|
||||
- Запуск от непривилегированного пользователя
|
||||
- Минимальные права доступа
|
||||
- Регулярные обновления зависимостей
|
||||
|
||||
## 6. Мониторинг и логирование
|
||||
|
||||
### 6.1 Структурированное логирование
|
||||
```go
|
||||
// internal/logger/logger.go
|
||||
func SetupLogger(level, format string) *logrus.Logger {
|
||||
logger := logrus.New()
|
||||
|
||||
// Уровень логирования
|
||||
if level, err := logrus.ParseLevel(level); err == nil {
|
||||
logger.SetLevel(level)
|
||||
}
|
||||
|
||||
// Формат логирования
|
||||
if format == "json" {
|
||||
logger.SetFormatter(&logrus.JSONFormatter{})
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Health Check
|
||||
```go
|
||||
// internal/handler/health.go
|
||||
func (h *HealthHandler) HealthCheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"version": "1.0.0",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Развертывание
|
||||
|
||||
### 7.1 Скрипт сборки
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/build.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building webhook server..."
|
||||
|
||||
# Сборка Docker образа
|
||||
docker build -t webhook-server:latest .
|
||||
|
||||
echo "Build completed successfully"
|
||||
```
|
||||
|
||||
### 7.2 Скрипт деплоя
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/deploy.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Deploying webhook server..."
|
||||
|
||||
# Остановка существующих контейнеров
|
||||
docker-compose down
|
||||
|
||||
# Запуск новых контейнеров
|
||||
docker-compose up -d
|
||||
|
||||
echo "Deployment completed successfully"
|
||||
```
|
||||
|
||||
## 8. Тестирование
|
||||
|
||||
### 8.1 Unit тесты
|
||||
```go
|
||||
// internal/handler/webhook_test.go
|
||||
func TestWebhookHandler_HandleWebhook(t *testing.T) {
|
||||
// Тесты валидации webhook
|
||||
// Тесты обработки ошибок
|
||||
// Тесты успешной обработки
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Integration тесты
|
||||
```go
|
||||
// tests/integration_test.go
|
||||
func TestFullDeploymentFlow(t *testing.T) {
|
||||
// Тест полного цикла деплоя
|
||||
// Тест обработки ошибок
|
||||
// Тест метрик
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Документация API
|
||||
|
||||
### 9.1 Endpoints
|
||||
|
||||
**POST /webhook**
|
||||
- Обработка webhook от Git
|
||||
- Валидация подписи
|
||||
- Асинхронная обработка
|
||||
|
||||
**GET /health**
|
||||
- Health check endpoint
|
||||
- Статус сервиса
|
||||
- Версия приложения
|
||||
|
||||
**GET /metrics**
|
||||
- Prometheus метрики
|
||||
- Метрики сборки
|
||||
- Метрики webhook
|
||||
|
||||
### 9.2 Примеры запросов
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:8080/health
|
||||
|
||||
# Метрики
|
||||
curl http://localhost:9090/metrics
|
||||
|
||||
# Webhook (пример)
|
||||
curl -X POST http://localhost:8080/webhook \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-GitHub-Event: push" \
|
||||
-d '{"ref":"refs/heads/main"}'
|
||||
```
|
||||
222
Идеи/Оптимизация ресурсов VPS/План переезда на Hugo.md
Normal file
222
Идеи/Оптимизация ресурсов VPS/План переезда на Hugo.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# План переезда с Quartz на Hugo
|
||||
|
||||
## 1. Обоснование переезда
|
||||
|
||||
### 1.1 Проблемы текущего решения (Quartz)
|
||||
- **Высокое потребление ресурсов**: Node.js + множество зависимостей
|
||||
- **Медленная сборка**: Особенно при большом количестве заметок
|
||||
- **Сложность настройки**: Множество конфигурационных файлов
|
||||
- **Ограниченная кастомизация**: Зависимость от готовых компонентов
|
||||
- **Проблемы с производительностью**: На VPS с ограниченными ресурсами
|
||||
|
||||
### 1.2 Преимущества Hugo
|
||||
- **Быстрая сборка**: Написан на Go, компилируется в бинарник
|
||||
- **Низкое потребление ресурсов**: Минимальные требования к CPU/RAM
|
||||
- **Простота развертывания**: Один бинарник + статические файлы
|
||||
- **Гибкость**: Полный контроль над шаблонами и стилями
|
||||
- **SEO-оптимизация**: Встроенные возможности для SEO
|
||||
- **Богатая экосистема**: Множество готовых тем и плагинов
|
||||
|
||||
## 2. Архитектура нового решения
|
||||
|
||||
### 2.1 Общая схема
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
A[Git Repository] --> B[Webhook Server Go]
|
||||
B --> C[Hugo Builder]
|
||||
C --> D[Static Files]
|
||||
D --> E[Nginx Server]
|
||||
F[Traefik Proxy] --> E
|
||||
F --> B
|
||||
|
||||
subgraph "Second Mind Container"
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
subgraph "External Services"
|
||||
A
|
||||
F
|
||||
end
|
||||
```
|
||||
|
||||
### 2.2 Компоненты системы
|
||||
|
||||
**Webhook Server (Go)**
|
||||
- Обработка webhook от Git
|
||||
- Запуск Hugo сборки
|
||||
- Управление процессом деплоя
|
||||
- Логирование и мониторинг
|
||||
|
||||
**Hugo Builder**
|
||||
- Генерация статических файлов
|
||||
- Обработка Markdown
|
||||
- Применение темы и стилей
|
||||
- Оптимизация ресурсов
|
||||
|
||||
**Nginx Server**
|
||||
- Раздача статических файлов
|
||||
- Кэширование
|
||||
- Gzip сжатие
|
||||
- SSL/TLS терминация
|
||||
|
||||
## 3. Техническая реализация
|
||||
|
||||
### 3.1 Webhook Server на Go
|
||||
|
||||
**Структура проекта:**
|
||||
```
|
||||
webhook-server/
|
||||
├── cmd/
|
||||
│ └── server/
|
||||
│ └── main.go
|
||||
├── internal/
|
||||
│ ├── handler/
|
||||
│ │ └── webhook.go
|
||||
│ ├── builder/
|
||||
│ │ └── hugo.go
|
||||
│ └── config/
|
||||
│ └── config.go
|
||||
├── pkg/
|
||||
│ └── git/
|
||||
│ └── client.go
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
**Основные функции:**
|
||||
- Валидация webhook payload
|
||||
- Клонирование/обновление репозитория
|
||||
- Запуск Hugo сборки
|
||||
- Обработка ошибок и retry логика
|
||||
- Метрики и логирование
|
||||
|
||||
### 3.2 Hugo конфигурация
|
||||
|
||||
**Структура Hugo проекта:**
|
||||
```
|
||||
hugo-site/
|
||||
├── config.toml
|
||||
├── content/
|
||||
│ ├── notes/
|
||||
│ ├── daily/
|
||||
│ └── templates/
|
||||
├── layouts/
|
||||
│ ├── _default/
|
||||
│ ├── partials/
|
||||
│ └── shortcodes/
|
||||
├── static/
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── images/
|
||||
└── themes/
|
||||
└── custom-theme/
|
||||
```
|
||||
|
||||
**Оптимизации:**
|
||||
- Минификация CSS/JS
|
||||
- Оптимизация изображений
|
||||
- Кэширование статических ресурсов
|
||||
- Lazy loading для изображений
|
||||
|
||||
### 3.3 Docker контейнеризация
|
||||
|
||||
**Dockerfile для webhook сервера:**
|
||||
```dockerfile
|
||||
FROM golang:1.21-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates hugo
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /app/main .
|
||||
EXPOSE 8080
|
||||
CMD ["./main"]
|
||||
```
|
||||
|
||||
## 4. План миграции
|
||||
|
||||
### 4.1 Этап 1: Подготовка (1-2 дня)
|
||||
- [ ] Создание Hugo проекта
|
||||
- [ ] Настройка базовой темы
|
||||
- [ ] Миграция контента из Quartz
|
||||
- [ ] Настройка URL структуры
|
||||
|
||||
### 4.2 Этап 2: Webhook Server (2-3 дня)
|
||||
- [ ] Разработка Go webhook сервера
|
||||
- [ ] Интеграция с Git API
|
||||
- [ ] Настройка Hugo сборки
|
||||
- [ ] Тестирование в dev окружении
|
||||
|
||||
### 4.3 Этап 3: Развертывание (1 день)
|
||||
- [ ] Настройка Docker контейнеров
|
||||
- [ ] Конфигурация Nginx
|
||||
- [ ] Настройка SSL сертификатов
|
||||
- [ ] Тестирование production окружения
|
||||
|
||||
### 4.4 Этап 4: Переключение (1 день)
|
||||
- [ ] Обновление DNS записей
|
||||
- [ ] Мониторинг производительности
|
||||
- [ ] Резервное копирование старого решения
|
||||
- [ ] Документирование изменений
|
||||
|
||||
## 5. Ожидаемые улучшения
|
||||
|
||||
### 5.1 Производительность
|
||||
- **Время сборки**: С 30-60 секунд до 5-10 секунд
|
||||
- **Потребление памяти**: С 512MB до 128MB
|
||||
- **CPU нагрузка**: Снижение на 70-80%
|
||||
- **Время загрузки страниц**: Улучшение на 40-60%
|
||||
|
||||
### 5.2 Операционные улучшения
|
||||
- **Простота развертывания**: Один контейнер вместо множества сервисов
|
||||
- **Мониторинг**: Встроенные метрики Go приложения
|
||||
- **Безопасность**: Меньше зависимостей = меньше уязвимостей
|
||||
- **Масштабируемость**: Легкое горизонтальное масштабирование
|
||||
|
||||
## 6. Риски и митигация
|
||||
|
||||
### 6.1 Технические риски
|
||||
- **Потеря функциональности**: Тщательное тестирование всех компонентов
|
||||
- **Проблемы с темой**: Создание собственной темы на основе существующей
|
||||
- **SEO регрессия**: Сохранение URL структуры и метаданных
|
||||
|
||||
### 6.2 Операционные риски
|
||||
- **Downtime при переключении**: Использование blue-green deployment
|
||||
- **Проблемы с контентом**: Полное резервное копирование
|
||||
- **Производительность**: Мониторинг и оптимизация
|
||||
|
||||
## 7. Мониторинг и метрики
|
||||
|
||||
### 7.1 Ключевые метрики
|
||||
- Время сборки Hugo
|
||||
- Размер генерируемых файлов
|
||||
- Время ответа webhook сервера
|
||||
- Потребление ресурсов контейнеров
|
||||
- Время загрузки страниц
|
||||
|
||||
### 7.2 Алерты
|
||||
- Сборка не завершилась в течение 60 секунд
|
||||
- Ошибки webhook сервера
|
||||
- Высокое потребление ресурсов
|
||||
- Недоступность сайта
|
||||
|
||||
## 8. Документация и обучение
|
||||
|
||||
### 8.1 Техническая документация
|
||||
- Архитектура системы
|
||||
- Конфигурация компонентов
|
||||
- Процедуры развертывания
|
||||
- Troubleshooting guide
|
||||
|
||||
### 8.2 Операционная документация
|
||||
- Процедуры мониторинга
|
||||
- План аварийного восстановления
|
||||
- Процедуры обновления
|
||||
- Контакты ответственных лиц
|
||||
Reference in New Issue
Block a user