Руководство по установке и администрированию

Для того, кто ставит сайт на сервер и ведёт его содержимое. Ниже — вся схема: установка, настройка, обновление и работа в админке.

Как устроен сайт

Сайт — это ASP.NET Core (Razor Pages) на .NET 10, без базы данных и без JavaScript на публичных страницах. Весь контент лежит файлами в git-репозитории: репозиторий одновременно и база данных, и бэкап, и механизм обновления.

Разделы сайта модульные: набор, порядок, названия и адреса задаются в Content/site.json через админку «Структура». Один тип раздела можно включить несколько раз (например, два файловых архива). У раздела две разные галки: «раздел работает» и «показывать в меню». Выключенный раздел не открывается по адресу (404) и не находится поиском; а рабочий раздел можно убрать из меню, оставив живым — так устроен поиск: форма стоит в шапке, а пункта меню у него нет.

Установка на сервер

Требования: Ubuntu 26, от 1 ГБ RAM (обязательно swap 2 ГБ — иначе сборка на сервере может падать по памяти), .NET SDK 10, nginx. Домен (и поддомен www) должен указывать на сервер A-записями.

Самый простой путь — скрипт deploy/install.sh:

  1. Скопируйте репозиторий (или папку deploy/) на сервер.
  2. Заполните поля вверху скрипта: минимум SSH_USER (ваш SSH-пользователь) и ADMIN_PASSWORD (пароль входа в админку); остальные поля уже заполнены разумными значениями.
  3. Запустите:
    sudo bash deploy/install.sh

Скрипт делает всё: swap, ставит .NET SDK 10, создаёт системного пользователя viruzober и bare-репозиторий /srv/git/viruzober.git, ставит post-receive хук, systemd-сервис, nginx, создаёт файл секретов и запускает certbot (HTTPS). Повторный запуск безопасен: уже сделанные шаги пропускаются. Сервис стартует только после первого git push — хук рестартует его сам.

Если ставите вручную — те же шаги расписаны по порядку в deploy/README.md: swap, пользователь и каталоги, bare-репозиторий, хук, разрешение на рестарт сервиса через sudo visudo, systemd, nginx, certbot.

После установки — первый пуш с вашей машины:

git remote add origin ssh://ВАШ_ПОЛЬЗОВАТЕЛЬ@СЕРВЕР/srv/git/viruzober.git
git push -u origin main

Первый пуш запускает сборку на сервере (около минуты на одном ядре; в это время сайт может не отвечать), дальше — только перезапуск сервиса.

Настройка: пароль, почта, git

Секреты живут в одном файле на сервере — /srv/www/viruzober/appsettings.Production.json. В git он не попадает никогда (в .gitignore), права на файл — только у пользователя сервиса (chmod 600). Обновления через push этот файл не трогают.

{
  "Admin": { "Password": "ПРИДУМАЙТЕ_НАДЁЖНЫЙ_ПАРОЛЬ" },
  "Mail": { "To": "адрес@куда-приходят-письма" },
  "Git": {
    "RepositoryDir": "/srv/git/viruzober.git",
    "AuthorName": "Viruzober",
    "AuthorEmail": "admin@viruzober.com"
  }
}

Обновление сайта

Изменения кода публикуются только через git push: хук на сервере делает checkout, собирает проект и перезапускает сервис. Контент при этом читается прямо из рабочего дерева, так что правки админки видны сразу.

Админка на сервере коммитит в тот же bare-репозиторий, поэтому изменения не теряются при следующем пуше. Одно предостережение: checkout при пуше перетирает незакоммиченные изменения в дереве — админка коммитит сразу после каждого сохранения, окно маленькое, но не сохраняйте ничего в админке в момент пуша.

Вход в админку

Адрес — нестандартный (не /admin), задаётся при установке сайта и хранится в коде сервера — здесь намеренно не публикуется, это защита от автоматического перебора типовых адресов админки ботами. Пароль — из Admin:Password. Ссылка «Админка» в навигации сайта появляется после входа. На страницах админки меню сайта нет — сверху «Viruzober — админка» и ссылка «На сайт», навигация инструментов внутри.

Внутри админки пять экранов, и они не дублируют друг друга:

«Модули» — это и есть первый экран админки: после входа открывается он. Когда был последний коммит, написано в шапке админки на любом экране.

«Наполнить» всегда открывает именно тот раздел, на строке которого вы стоите — даже если разделов одного типа несколько (два раздела статей, например).

Структура разделов

Админка → «Структура» — дерево всех разделов сайта в том порядке, в каком они стоят в меню. Что здесь можно:

Типы разделов:

ТипЧто этоНастройки
Раздел-плагинСодержимое рисует плагин из Plugins/: статьи, программы, файлы, обратная связь, поиск — что установленовыбор плагина и его настройки; каталог данных сайт выдаёт сам
СтраницаСтраница из блоков: текст, встроенный сервис, ссылка, виджет плагина — в любом порядкебез пути к файлу — блоки хранятся в структуре
СсылкаПункт меню — переход на любой адресURL: /… — свой сайт, https://… — внешний
СервисОтдельное приложение на этом же домене (конвертер, редактор — что угодно), показывается во фрейме поверх шапки и подвала; под фреймом — ссылка открыть сервис в отдельной вкладке (запасной путь для скринридера)адрес выбирается из списка сервисов (манифесты Services/*/manifest.json в репозитории) — адрес /s/имя подставится сам; руками — только если сервиса в списке нет
ГруппаПункт меню без своего содержимого — только чтобы собрать несколько разделов (например, несколько «Сервисов») в подменюбез контента

Отдельного типа «Главная» нет: главная — это роль, а не сущность. Главной становится тот раздел, у которого не задан адрес: он и открывается по «/». Ею может быть «Страница», «Группа» или «Раздел-плагин»; главная на сайте одна — если адрес пуст уже у другого раздела, форма об этом скажет и адрес попросит. Главную можно переназначить (убрать адрес у одного раздела и задать другому) и удалить, как любой раздел. Пока главной нет, на «/» показывается заглушка из админки → «Пустой сайт», а не пустой 404.

У «Ссылки» и «Сервиса» не бывает подразделов; у «Группы», наоборот, могут быть любые дети, включая «Ссылку» и «Сервис» — так собирается подменю. Описание раздела (необязательное, до 300 символов) показывается под его ссылкой в списке вложенных — так страница группы превращается в подменю с названиями и описаниями. Если экземпляров одного типа несколько, вверху страниц контента появляется выбор «Раздел» — переключение между ними.

Админка → «Модули» — обзор всех источников содержимого на одном экране: у каждого раздела видно его тип, в меню ли он (и у какого родителя), куда встроен блоками страниц, выключен ли, и его описание. Ниже — сервисы, у которых ещё нет раздела, со ссылкой «Создать раздел для этого сервиса» — форма откроется сразу с типом «Сервис» и выбранным сервисом.

Контент

Всё содержимое правится в админке; каждое сохранение коммитится в git.

Страницы

Админка → «Страницы» — раздел типа «Страница» (например, «О сайте» или главная) собирается из блоков — список с клавиатурой, как «Структура»: стрелками переставляются, «Настроить» открывает поля блока, «Удалить» — подтверждение. Блоки четырёх видов:

Так на одной странице можно собрать вступительный текст, сервис и ссылки — раньше страница могла быть только одним markdown-файлом. Встроить можно и выключенный раздел: в меню его нет и по адресу он не открывается, но на странице блоком работает (в списке выбора выключенные помечены «(выключен)»).

Разделы-плагины

Всё остальное содержимое ведут плагины: у каждого раздела в админке своя кнопка «Наполнить», и что там за экраны — решает плагин. Данные раздела лежат в Content/plugins/{id раздела}/, медиа — в wwwroot/media/{id раздела}/; каталог сайт выдаёт сам, руками путь нигде не вводится. Удаление раздела уносит и его данные (страница подтверждения скажет, сколько файлов).

Статьи

Статьи в markdown, сгруппированные в разделы. Каждая статья — файл {слаг}.md с шапкой в формате YAML:

---
title: Название статьи
date: 2026-08-22
description: Краткое описание (показывается в списке)
tags: [первый, второй]
---

Текст статьи в markdown.

Адрес статьи составляется из заголовка сам («Как я слушаю музыку» → kak-ya-slushayu-muzyku), при совпадении добавляется -2. У уже созданной статьи адрес можно поправить в поле «Слаг». Заголовки внутри текста начинаются с третьего уровня: первый — название раздела, второй — заголовок статьи.

В статьях бывают картинки, аудио и видео. Файлы загружаются полем «Загрузить медиафайлы» — в том числе у новой, ещё не сохранённой статьи: они уйдут вместе с первым сохранением, и вы останетесь в редакторе со списком файлов. Чтобы вставить файл в текст, поставьте курсор в нужное место и нажмите Ctrl+M (или кнопку «Вставить в текст» у файла) — сайт сам подставит ![описание](media:имя-файла) и поставит курсор на слово «описание». По расширению сайт сам покажет изображение, плеер или ссылку на скачивание. Описание у картинок обязательно (его озвучивает скринридер). Если JavaScript выключен, рядом с каждым файлом напечатан готовый фрагмент — его можно скопировать в текст руками.

Программы

Каталог программ: категории, внутри — подкатегории (один уровень) и программы. У программы: название, краткое описание, полное описание (markdown) и таблица версий. Каждая версия — это версия, дата, имя файла, размер и заметки об изменениях; сам файл загружается в медиа раздела. На странице программы версии показываются таблицей со ссылками «Скачать». Адрес программы или категории сайт составляет сам из названия («Читалка книг» → chitalka-knig).

Файлы

Файловый архив: папки, загрузка нескольких файлов сразу, переименование и удаление. На сайте раздел отдаёт листинг папок и скачивание файлов. Имена файлов и папок: без / и \, не . и .., не начинаются с точки (такие файлы скрыты из листинга).

Обратная связь

Форма письма: имя, адрес, текст. Письмо уходит через postfix на сервере на адрес из Mail:To. Одно письмо в минуту с адреса — потолок держит сам сайт.

Место в шапке и в подвале сайта плагин просит сам, и виден такой кусок на каждой странице (так стоит форма поиска). А виджет — наоборот: где ему быть, решаете вы, блоком на нужной странице.

Поиск

Поиск — тоже раздел-плагин, и форму в шапке сайта рисует он сам: плагин просит место в шапке, сайт это место даёт. Если раздела поиска нет или он выключен, формы в шапке просто не будет; а вот «показывать в меню» на неё не влияет — форма остаётся, даже когда пункта меню нет. Ищет по страницам и по всем разделам-плагинам — только по включённым.

Горячие клавиши в поле текста

В любом поле markdown в админке (текстовый блок страницы, заглушка пустого сайта) работают сочетания. Выделите текст и нажмите — оформление применится, повторное нажатие снимет его. Результат каждого действия озвучивается, фокус остаётся в тексте; Ctrl+Z отменяет как обычно. Раскладка не важна.

Список сочетаний есть и на самой странице — раскрывающийся блок «Сочетания клавиш в поле текста» под полем. Без JavaScript сочетаний нет: разметка набирается руками, как раньше.

Плагины

Плагин — папка в Plugins/ с кодом на Lua: он добавляет свой тип модуля. Админка → «Плагины» показывает все папки, какая версия установлена, сколько на плагине разделов, и — главное — что плагин о себе заявляет: пишет ли файлы, шлёт ли письма, ходит ли в интернет, ищет ли по сайту. Это заявление о намерениях, а не запрет: плагины ставит владелец сайта и только свои. Установка и обновление — кнопкой на этом же экране; установленный плагин появляется в списке типов модуля при создании раздела.

Скрипт разметкой плагин вернуть не может — такую разметку сайт не пропускает вообще. Свой JavaScript у плагина всё же бывает (например, у плеера, который весь работает в браузере), но не спрятанным в разметке: файл лежит в папке плагина и назван в его манифесте, тег подключает сам сайт — и только на страницах этого плагина. В списке заявок на экране «Плагины» это видно строкой вида «подключает свой скрипт js/app.js — он работает в браузере посетителя». Страница обязана оставаться рабочей и без скрипта: JavaScript на сайте — добавка, а не условие. Редактор разметки в админке (см. ниже) подключает сайт — тоже по просьбе плагина.

Git и бэкапы

Локальная разработка

На своей машине сайт поднимается одной командой из каталога репозитория:

dotnet run

http://localhost:5000. Адрес админки локально — тот же секретный путь, что и на сервере (см. deploy/README.md в репозитории), тестовый пароль — dev-password (задан в appsettings.Development.json, используется только для разработки; на сервере пароль свой — в appsettings.Production.json). Контент читается из файлов при каждом запросе: файл, положенный в Content/ вручную, виден сразу, перезапуск не нужен.

Админка с NVDA

Все формы подписаны, сообщения об ошибках и подтверждениях озвучиваются автоматически (в NVDA для этого должна быть включена настройка «Сообщать динамические изменения содержимого» — она включена по умолчанию). Отдельно — дерево в «Структуре»:

Лучше всего сайт работает в Firefox с последней версией NVDA.