Added soft page
This commit is contained in:
parent
111af09ec7
commit
2c702f1f92
11 changed files with 412 additions and 4 deletions
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
1. go Справочник по языку Go
|
||||
Установка, синтаксис, типы, функции, горутины...
|
||||
5. soft Справочник по софту
|
||||
Обзоры используемого софта: Zola, Forgejo...
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ var All = []Book{
|
|||
LinuxBook,
|
||||
DevOpsBook,
|
||||
InfoSecBook,
|
||||
SoftBook,
|
||||
}
|
||||
|
||||
// Find ищет книгу по ключу или алиасу
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"rubric/modules/gomodules"
|
||||
"rubric/modules/infosecmodules"
|
||||
"rubric/modules/linuxmodules"
|
||||
"rubric/modules/softmodules"
|
||||
"rubric/utils"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -79,6 +80,16 @@ func buildSearchIndex() []searchEntry {
|
|||
})
|
||||
}
|
||||
|
||||
// Soft
|
||||
for _, m := range softmodules.All {
|
||||
entries = append(entries, searchEntry{
|
||||
bookKey: "soft", bookTitle: "Soft",
|
||||
modKey: m.Key, modTitle: m.Title, modDesc: m.Description,
|
||||
keywords: softmodules.Keywords(m.Key),
|
||||
run: m.Run,
|
||||
})
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
|
|
@ -196,6 +207,8 @@ func bookColorFor(key string) string {
|
|||
return utils.ColorMagenta
|
||||
case "infosec":
|
||||
return utils.ColorRed
|
||||
case "soft":
|
||||
return utils.ColorBlue
|
||||
}
|
||||
return utils.ColorWhite
|
||||
}
|
||||
|
|
|
|||
99
catalog/softbook.go
Normal file
99
catalog/softbook.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package catalog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"rubric/modules/softmodules"
|
||||
"rubric/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var SoftBook = Book{
|
||||
Key: "soft",
|
||||
Aliases: []string{"sw", "software"},
|
||||
Title: "Справочник по софту",
|
||||
Description: "Обзоры используемого софта: Zola, Forgejo и другое",
|
||||
Run: runSoftBook,
|
||||
}
|
||||
|
||||
func runSoftBook(scanner *bufio.Scanner) {
|
||||
for {
|
||||
showSoftMenu()
|
||||
|
||||
input, ok := ReadLine(scanner)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if input == "" {
|
||||
continue
|
||||
}
|
||||
if IsQuit(input) {
|
||||
fmt.Printf("\n %s%sДо свидания!%s\n\n",
|
||||
utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||
return
|
||||
}
|
||||
if IsBack(input) {
|
||||
return
|
||||
}
|
||||
|
||||
if n, err := strconv.Atoi(input); err == nil {
|
||||
if n >= 1 && n <= len(softmodules.All) {
|
||||
softmodules.All[n-1].Run()
|
||||
if softWaitBack(scanner) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n",
|
||||
utils.ColorBold, utils.ColorRed, n, len(softmodules.All), utils.ColorReset)
|
||||
continue
|
||||
}
|
||||
|
||||
if m, ok := softmodules.Find(input); ok {
|
||||
m.Run()
|
||||
if softWaitBack(scanner) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("\n %s%sНе понял '%s'. Номер, ключ раздела, b — назад.%s\n",
|
||||
utils.ColorBold, utils.ColorRed, input, utils.ColorReset)
|
||||
}
|
||||
}
|
||||
|
||||
// softWaitBack возвращает true если пользователь нажал b (выйти в главное меню)
|
||||
func softWaitBack(scanner *bufio.Scanner) bool {
|
||||
fmt.Printf("\n %s%s[Enter — назад в меню Soft, b — главное меню]%s ",
|
||||
utils.ColorDim, utils.ColorCyan, utils.ColorReset)
|
||||
if scanner.Scan() {
|
||||
return IsBack(strings.TrimSpace(scanner.Text()))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func showSoftMenu() {
|
||||
utils.Header("Справочник Soft")
|
||||
|
||||
fmt.Printf(" %s%sРазделы:%s\n\n", utils.ColorBold, utils.ColorWhite, utils.ColorReset)
|
||||
|
||||
for i, m := range softmodules.All {
|
||||
aliases := ""
|
||||
if len(m.Aliases) > 0 {
|
||||
aliases = fmt.Sprintf("%s [%s]%s",
|
||||
utils.ColorDim, strings.Join(m.Aliases, ", "), utils.ColorReset)
|
||||
}
|
||||
fmt.Printf(" %s%s %2d.%s %s%-10s%s %s%-32s%s%s\n",
|
||||
utils.ColorBold, utils.ColorCyan, i+1, utils.ColorReset,
|
||||
utils.ColorYellow+utils.ColorBold, m.Key, utils.ColorReset,
|
||||
utils.ColorWhite, m.Title, utils.ColorReset,
|
||||
aliases,
|
||||
)
|
||||
fmt.Printf(" %s%s%s\n\n",
|
||||
utils.ColorDim, m.Description, utils.ColorReset)
|
||||
}
|
||||
|
||||
fmt.Printf(" %s%sРаздел (номер / ключ / b — главное меню / q — выход):%s ",
|
||||
utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||||
}
|
||||
12
main.go
12
main.go
|
|
@ -10,12 +10,13 @@ import (
|
|||
"rubric/modules/gomodules"
|
||||
"rubric/modules/infosecmodules"
|
||||
"rubric/modules/linuxmodules"
|
||||
"rubric/modules/softmodules"
|
||||
"rubric/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
book := flag.String("book", "", "открыть справочник (go, linux, devops, infosec)")
|
||||
book := flag.String("book", "", "открыть справочник (go, linux, devops, infosec, soft)")
|
||||
topic := flag.String("topic", "", "открыть раздел (используется вместе с -book)")
|
||||
search := flag.String("search", "", "поиск по всем справочникам")
|
||||
list := flag.Bool("list", false, "список всех справочников")
|
||||
|
|
@ -102,6 +103,15 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
m.Run()
|
||||
case "soft", "sw", "software":
|
||||
m, ok := softmodules.Find(*topic)
|
||||
if !ok {
|
||||
fmt.Printf("\n %s%sРаздел '%s' не найден в Soft.%s\n",
|
||||
utils.ColorBold, utils.ColorRed, *topic, utils.ColorReset)
|
||||
softmodules.ListModules()
|
||||
os.Exit(1)
|
||||
}
|
||||
m.Run()
|
||||
default:
|
||||
fmt.Printf("\n %s%sСправочник '%s' не найден.%s\n",
|
||||
utils.ColorBold, utils.ColorRed, *book, utils.ColorReset)
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ func Keywords(key string) []string {
|
|||
"provider", "ресурс", "resource", "state", "план", "plan", "apply",
|
||||
"destroy", "модуль", "module", "workspace", "backend", "S3",
|
||||
"AWS", "GCP", "Azure", "переменные", "output",
|
||||
"opentofu", "tofu", "форк", "fork", "MPL", "BSL", "лицензия",
|
||||
"license", "Linux Foundation", "state encryption", "шифрование state",
|
||||
},
|
||||
"monitoring": {
|
||||
"мониторинг", "Prometheus", "Grafana", "AlertManager", "Loki",
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import (
|
|||
|
||||
var TerraformModule = Module{
|
||||
Key: "terraform",
|
||||
Aliases: []string{"tf", "iac", "infra"},
|
||||
Title: "Terraform — инфраструктура как код",
|
||||
Description: "HCL, провайдеры, ресурсы, state, модули, workspace",
|
||||
Aliases: []string{"tf", "iac", "infra", "opentofu", "tofu"},
|
||||
Title: "Terraform / OpenTofu — инфраструктура как код",
|
||||
Description: "HCL, провайдеры, ресурсы, state, модули, workspace, форк OpenTofu",
|
||||
Run: showTerraform,
|
||||
}
|
||||
|
||||
|
|
@ -255,6 +255,61 @@ terraform workspace new staging
|
|||
terraform workspace select production
|
||||
# В коде: terraform.workspace → "staging" / "production"`)
|
||||
|
||||
utils.Section("OpenTofu — форк Terraform")
|
||||
utils.Code(`OpenTofu — открытый форк Terraform, созданный в 2023 году после того,
|
||||
как HashiCorp сменил лицензию Terraform с MPL 2.0 на BSL 1.1 (Business Source License).
|
||||
Форк собран из последней MPL-версии Terraform (1.5.x) и живёт под управлением
|
||||
Linux Foundation — то есть под вендор-нейтральным, многосторонним управлением,
|
||||
а не под контролем одной компании.
|
||||
|
||||
# Установка:
|
||||
curl --proto '=https' --tlsv1.2 -fsSL \
|
||||
https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
|
||||
chmod +x install-opentofu.sh
|
||||
./install-opentofu.sh --install-method standalone
|
||||
|
||||
tofu version
|
||||
|
||||
# Совместимость: тот же HCL, тот же формат state, те же провайдеры —
|
||||
# миграция практически всегда сводится к замене бинаря:
|
||||
alias terraform=tofu`)
|
||||
|
||||
utils.Section("Почему в 2026 лучше выбрать OpenTofu")
|
||||
utils.Code(`1. Лицензия
|
||||
OpenTofu — MPL 2.0, одобрена OSI, без коммерческих ограничений.
|
||||
Terraform (с версии 1.6) — BSL 1.1, не одобрена OSI. BSL не мешает
|
||||
обычному использованию "terraform apply" для своей инфраструктуры,
|
||||
но запрещает предлагать Terraform как конкурирующий управляемый сервис —
|
||||
это касается вендоров SaaS/managed-платформ, не рядовых инженеров.
|
||||
|
||||
2. Управление (governance)
|
||||
OpenTofu развивается комитетом под эгидой Linux Foundation —
|
||||
роадмап не завязан на коммерческие приоритеты одной компании.
|
||||
Terraform принадлежит HashiCorp (с 2025 года — в составе IBM).
|
||||
|
||||
3. Функции, которых нет в открытом Terraform CLI:
|
||||
- нативное шифрование state и plan-файлов (KMS/PBKDF2) —
|
||||
важно, если бэкенду state нельзя полностью доверять
|
||||
- provider for_each — итерация по нескольким конфигурациям одного провайдера
|
||||
- раннее вычисление переменных, в т.ч. в конфигурации backend
|
||||
- флаг -exclude (точечное исключение ресурсов из plan/apply)
|
||||
- собственный открытый реестр провайдеров/модулей registry.opentofu.org
|
||||
|
||||
4. Открытость без риска vendor lock-in
|
||||
Если однажды понадобится вернуться на Terraform — это тоже
|
||||
практически замена бинаря, конфигурация и state совместимы.
|
||||
|
||||
Что осталось только в Terraform:
|
||||
Terraform Stacks и часть функций тесно интегрированы с HCP Terraform
|
||||
(управляемой платформой HashiCorp) — в открытом бинаре их нет.
|
||||
|
||||
Итог: для новых проектов в 2026 OpenTofu — выбор по умолчанию с меньшим риском:
|
||||
идентичный синтаксис, та же экосистема провайдеров, плюс нативное шифрование
|
||||
state и вендор-нейтральное управление. Если уже есть большая инвестиция в
|
||||
Terraform/HCP и используются Stacks — есть смысл остаться или мигрировать не спеша.`)
|
||||
|
||||
utils.Note("tofu — почти всегда совместимая замена terraform: 'alias terraform=tofu', большинство конфигураций и .tfstate работают без изменений")
|
||||
|
||||
utils.Section("Лучшие практики")
|
||||
utils.Code(`Структура проекта:
|
||||
infra/
|
||||
|
|
|
|||
82
modules/softmodules/soft_forgejo.go
Normal file
82
modules/softmodules/soft_forgejo.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package softmodules
|
||||
|
||||
import (
|
||||
"rubric/utils"
|
||||
)
|
||||
|
||||
var ForgejoModule = Module{
|
||||
Key: "forgejo",
|
||||
Aliases: []string{"fj", "git-forge"},
|
||||
Title: "Forgejo — самохостинг git-форжа",
|
||||
Description: "Форк Gitea: Docker-развёртывание, Angie, SSH на нестандартном порту",
|
||||
Run: showForgejo,
|
||||
}
|
||||
|
||||
func showForgejo() {
|
||||
utils.Header("Soft — Forgejo")
|
||||
|
||||
utils.Section("Что это")
|
||||
utils.Code(`Forgejo — самохостимый git-форж, форк Gitea. Свой репозиторий кода
|
||||
с веб-интерфейсом, issues, pull request'ами, вики и (опционально)
|
||||
Forgejo Actions для CI/CD — как собственный GitHub/GitLab на своём VDS.
|
||||
|
||||
Развёрнут на git.thebookofmagnus.ru (пользователь mroot),
|
||||
за Angie как реверс-прокси, SSH-доступ на порту 222.`)
|
||||
|
||||
utils.Section("Развёртывание через Docker")
|
||||
utils.Code(`# docker-compose.yml (упрощённо, с SQLite — не нужен отдельный БД-сервер)
|
||||
services:
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:latest
|
||||
environment:
|
||||
- FORGEJO__database__DB_TYPE=sqlite3
|
||||
- FORGEJO__server__DOMAIN=git.thebookofmagnus.ru
|
||||
- FORGEJO__server__SSH_PORT=222
|
||||
- FORGEJO__service__DISABLE_REGISTRATION=true
|
||||
volumes:
|
||||
- ./data:/data
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
- "222:22"
|
||||
restart: unless-stopped`)
|
||||
|
||||
utils.Section("Angie как реверс-прокси")
|
||||
utils.Code(`server {
|
||||
listen 443 ssl;
|
||||
server_name git.thebookofmagnus.ru;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/git.thebookofmagnus.ru/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/git.thebookofmagnus.ru/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP-блок для certbot (webroot-метод, --nginx с Angie не работает):
|
||||
server {
|
||||
listen 80;
|
||||
server_name git.thebookofmagnus.ru;
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
}`)
|
||||
|
||||
utils.Section("Частые проблемы при развёртывании")
|
||||
utils.Println("• certbot: NXDOMAIN — DNS A-запись ещё не разошлась, подождать пропагации")
|
||||
utils.Println("• certbot: 404 на /.well-known/acme-challenge/ — не хватает HTTP(80) server-блока в Angie для сабдомена")
|
||||
utils.Println("• До готовности DNS/TLS — временный доступ через SSH-туннель: ssh -L 3000:127.0.0.1:3000 user@host")
|
||||
utils.Println("• Предупреждение о смене SSH host key при первом git push — решается добавлением ключа в ssh-agent")
|
||||
utils.Println("• Management → Users в новых версиях Forgejo переехал в раздел \"Identity & access\", а не отдельная вкладка")
|
||||
|
||||
utils.Section("Настройка")
|
||||
utils.Command("FORGEJO__service__DISABLE_REGISTRATION=true", "отключить самостоятельную регистрацию")
|
||||
utils.Command("FORGEJO__ui__APP_NAME", "название, отображаемое в заголовке вкладки браузера")
|
||||
utils.Command("/data/gitea/public/assets/img/", "путь для замены логотипа и favicon (в примонтированном volume)")
|
||||
|
||||
utils.Note("CI/CD (Forgejo Actions) можно не настраивать сразу — вполне рабочий вариант отложить его на потом")
|
||||
|
||||
utils.Footer()
|
||||
}
|
||||
75
modules/softmodules/soft_registry.go
Normal file
75
modules/softmodules/soft_registry.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package softmodules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"rubric/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Module описывает один раздел справочника Soft
|
||||
type Module struct {
|
||||
Key string
|
||||
Aliases []string
|
||||
Title string
|
||||
Description string
|
||||
Run func()
|
||||
}
|
||||
|
||||
// All — список всех модулей в порядке меню
|
||||
var All = []Module{
|
||||
ZolaModule,
|
||||
ForgejoModule,
|
||||
// сюда будут добавляться новые обзоры софта
|
||||
}
|
||||
|
||||
// Find ищет модуль по ключу или алиасу
|
||||
func Find(key string) (Module, bool) {
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
for _, m := range All {
|
||||
if m.Key == key {
|
||||
return m, true
|
||||
}
|
||||
for _, a := range m.Aliases {
|
||||
if a == key {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return Module{}, false
|
||||
}
|
||||
|
||||
// ListModules выводит краткий список модулей
|
||||
func ListModules() {
|
||||
fmt.Printf("\n%s%sРазделы справочника Soft:%s\n\n",
|
||||
utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||
for i, m := range All {
|
||||
fmt.Printf(" %s%d.%s %-14s — %s\n",
|
||||
utils.ColorDim, i+1, utils.ColorReset,
|
||||
utils.ColorYellow+utils.ColorBold+m.Key+utils.ColorReset,
|
||||
m.Title,
|
||||
)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Keywords возвращает ключевые слова для поиска по модулю
|
||||
func Keywords(key string) []string {
|
||||
kw := map[string][]string{
|
||||
"zola": {
|
||||
"zola", "статический сайт", "static site", "генератор сайта",
|
||||
"SSG", "Rust", "шаблоны", "templates", "Tera", "markdown",
|
||||
"сайт", "rsync", "деплой", "deploy", "front matter",
|
||||
"content", "themes", "темы",
|
||||
},
|
||||
"forgejo": {
|
||||
"forgejo", "git", "форж", "git forge", "gitea", "self-hosted",
|
||||
"самохостинг", "репозиторий", "repository", "docker", "sqlite",
|
||||
"SSH", "issues", "pull request", "CI", "actions", "webhook",
|
||||
"git-сервер",
|
||||
},
|
||||
}
|
||||
if v, ok := kw[key]; ok {
|
||||
return v
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
69
modules/softmodules/soft_zola.go
Normal file
69
modules/softmodules/soft_zola.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package softmodules
|
||||
|
||||
import (
|
||||
"rubric/utils"
|
||||
)
|
||||
|
||||
var ZolaModule = Module{
|
||||
Key: "zola",
|
||||
Aliases: []string{"z", "static"},
|
||||
Title: "Zola — генератор статических сайтов",
|
||||
Description: "Однобинарный SSG на Rust: сборка, деплой через rsync, Angie",
|
||||
Run: showZola,
|
||||
}
|
||||
|
||||
func showZola() {
|
||||
utils.Header("Soft — Zola")
|
||||
|
||||
utils.Section("Что это")
|
||||
utils.Code(`Zola — генератор статических сайтов (SSG), написанный на Rust.
|
||||
Один бинарь без зависимостей, встроенный шаблонизатор Tera,
|
||||
встроенный Sass-компилятор и Markdown-парсер — ничего ставить
|
||||
отдельно не нужно (в отличие от Jekyll/Hugo-плагинов и npm-тулчейнов).
|
||||
|
||||
Подходит для блогов, документации, персональных сайтов —
|
||||
как раз для thebookofmagnus.ru.`)
|
||||
|
||||
utils.Section("Основные команды")
|
||||
utils.Command("zola init mysite", "создать новый проект сайта")
|
||||
utils.Command("zola build", "собрать сайт в папку public/")
|
||||
utils.Command("zola serve", "локальный dev-сервер с live reload")
|
||||
utils.Command("zola check", "проверить ссылки и конфигурацию")
|
||||
|
||||
utils.Section("Модель развёртывания: локальная сборка + rsync")
|
||||
utils.Code(`Zola не обязательно ставить на сервер — она нужна только там,
|
||||
где собирается сайт. Рабочая схема:
|
||||
|
||||
1. Пишешь контент локально
|
||||
2. zola build → получаешь папку public/
|
||||
3. rsync -avz --delete public/ user@server:/var/www/mysite/public/
|
||||
4. Angie отдаёт статику из /var/www/mysite/public
|
||||
|
||||
Разбор флагов rsync:
|
||||
-a archive-режим: права, симлинки, время модификации
|
||||
-v verbose — видно что копируется
|
||||
-z сжатие при передаче
|
||||
--delete удаляет на сервере файлы, которых больше нет локально
|
||||
(важно при переименовании/удалении страниц)
|
||||
|
||||
Альтернатива — автосборка на сервере: bare git-репозиторий +
|
||||
post-receive хук, который сам делает checkout + zola build +
|
||||
копирование в /var/www/mysite/public. Проще для старта — вариант
|
||||
с локальной сборкой и rsync, без Zola на самом VDS.`)
|
||||
|
||||
utils.Section("Раздача через Angie")
|
||||
utils.Code(`server {
|
||||
listen 443 ssl;
|
||||
server_name thebookofmagnus.ru www.thebookofmagnus.ru;
|
||||
|
||||
root /var/www/mysite/public;
|
||||
index index.html;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/thebookofmagnus.ru/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/thebookofmagnus.ru/privkey.pem;
|
||||
}`)
|
||||
|
||||
utils.Note("certbot с Angie: плагин --nginx конфликтует с Angie. Всегда используй --webroot (webroot-папка вроде /var/www/certbot + location /.well-known/acme-challenge/ в HTTP-server-блоке)")
|
||||
|
||||
utils.Footer()
|
||||
}
|
||||
BIN
rubric
BIN
rubric
Binary file not shown.
Loading…
Add table
Reference in a new issue