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;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user