Add help-osk-docs stack: Zensical instructions site for 05-osk

Static instructions site for client 05 served at help.osk.team. Markdown +
images built by Zensical (zensical build) into public/, served by nginx behind
Traefik. Content is bind-mounted from /mnt/containers/help-osk-docs (absolute
host path — Portainer GitOps can't resolve relative mounts). Adds the
help/help-osk router+service to traefik/dynamic/web.yml (TLS via Let's Encrypt,
public/open).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 14:59:33 +03:00
co-authored by Claude Opus 4.8
parent fb58acea5e
commit 1d6be15448
9 changed files with 311 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Build output — generated by `zensical build`, not tracked.
/public/
.DS_Store
+69
View File
@@ -0,0 +1,69 @@
# help-osk-docs — instructions site for client 05
Public static site at **https://help.osk.team** with user instructions, written
in Markdown and built with [Zensical](https://github.com/zensical/zensical)
(static-site generator from the Material for MkDocs team). Served by `nginx`
behind the existing Traefik reverse proxy.
## How it works
- `docs/` — Markdown pages (+ `docs/assets/` for images). **This is the content.**
- `zensical.toml` — site config (name, nav, theme, Markdown extensions).
- `nginx.conf` — static web-server config.
- `docker-compose.yaml` — two services:
- `help-osk-build` — Zensical image, one-shot: runs `zensical build --clean`,
rendering `docs/``public/`, then exits. (The Zensical image is a
builder, **not** a host server — by design.)
- `help-osk``nginx:alpine`, serves `public/`, joins `reverseproxy-nw`.
Starts only after the build succeeds.
- Traefik terminates TLS for `help.osk.team` (Let's Encrypt) and proxies to the
`help-osk` container. Router/service live in
`../traefik/dynamic/web.yml` (`help` / `help-osk`).
## Bind-mount / host-sync (important)
Content is **bind-mounted**, and Portainer GitOps can't bind-mount the
repo checkout (relative paths resolve inside Portainer's container and become
empty dirs). So the project must exist on the host at an absolute path —
exactly like the Traefik config. This git repo is the **source of truth**.
Sync the project to the host (excluding the generated `public/`):
```sh
rsync -a --delete --exclude 'public/' \
help-osk-docs/ <docker-host>:/mnt/containers/help-osk-docs/
```
The `help-osk-build` container creates `/mnt/containers/help-osk-docs/public`
on the host during the build.
## Deploy (Portainer)
1. DNS: `help.osk.team` → public edge IP, reachable on :80 (Let's Encrypt
http-01). *(Done.)*
2. Sync this folder to `/mnt/containers/help-osk-docs/` on the docker host
(see above).
3. Make sure the Traefik `help` router/service in `../traefik/dynamic/web.yml`
is also synced to the host dynamic dir
(`/mnt/containers/traefik/container-data/dynamic/web.yml`). Traefik's file
provider (`watch: true`) picks it up live — no Traefik restart needed.
4. In Portainer, add a stack pointing at `help-osk-docs/docker-compose.yaml`
(or redeploy the existing stack). The builder runs, then nginx comes up.
## Add or edit an instruction
1. Add/edit a Markdown file in `docs/` (e.g. `docs/nastrojka-vpn.md`).
2. Put images in `docs/assets/`, reference them: `![alt](assets/file.png)`
(size with `![alt](assets/file.png){ width="600" }`).
3. Add the page to `nav` in `zensical.toml`.
4. Commit, re-sync to the host (step 2 above), redeploy the stack in Portainer
(re-runs the build; nginx serves the updated `public/`).
## Local preview (optional)
```sh
# Build and inspect output
docker run --rm -v "$PWD":/docs:Z zensical/zensical build --clean
# Live preview on http://localhost:8000
docker run --rm -p 8000:8000 -v "$PWD":/docs zensical/zensical
```
+37
View File
@@ -0,0 +1,37 @@
services:
# One-shot builder. Zensical's official image is for builds/previews only
# (NOT a host server), so we use it to render docs/ -> public/, then exit.
help-osk-build:
image: zensical/zensical:latest
container_name: help-osk-build
working_dir: /docs
command: ["build", "--clean"]
restart: "no"
volumes:
# Whole project (zensical.toml + docs/ + writes public/) on the host.
#
# NOTE: absolute host path (NOT ./relative). Portainer GitOps runs compose
# with paths relative to its own container (/data/compose/<id>/...), which
# the Docker daemon can't resolve — relative mounts silently become empty
# dirs. Keep /mnt/containers/help-osk-docs in sync with this repo (source
# of truth), same as the traefik config.
- /mnt/containers/help-osk-docs:/docs:z
# Static web server. Serves the generated public/ and joins the Traefik
# reverse-proxy network; Traefik terminates TLS for help.osk.team.
help-osk:
image: nginx:alpine
container_name: help-osk
restart: always
depends_on:
help-osk-build:
condition: service_completed_successfully
volumes:
- /mnt/containers/help-osk-docs/public:/usr/share/nginx/html:ro,z
- /mnt/containers/help-osk-docs/nginx.conf:/etc/nginx/conf.d/default.conf:ro,z
networks:
- reverseproxy-nw
networks:
reverseproxy-nw:
external: true
+2
View File
@@ -0,0 +1,2 @@
# Положите сюда картинки для инструкций (png/jpg/svg).
# Ссылка из Markdown: ![alt](assets/имя-файла.png)
+18
View File
@@ -0,0 +1,18 @@
---
icon: lucide/book-open
---
# Инструкции
Добро пожаловать в базу инструкций. Выберите нужный раздел в меню слева
или воспользуйтесь поиском вверху страницы.
## Разделы
- [Пример инструкции](primer-instrukcii.md) — шаблон страницы с картинками и подсказками.
!!! tip "Как добавить новую инструкцию"
Создайте Markdown-файл в папке `docs/`, добавьте его в `nav` в файле
`zensical.toml`, положите картинки в `docs/assets/` — и пересоберите сайт.
Подробнее — в `README.md` этого репозитория.
+55
View File
@@ -0,0 +1,55 @@
---
icon: lucide/file-text
---
# Пример инструкции
Эта страница — шаблон. Скопируйте её, переименуйте и наполните своим текстом.
## Шаги
1. Откройте нужное приложение.
2. Выполните действие.
3. Проверьте результат.
## Картинки
Положите изображение в `docs/assets/` и вставьте его так:
```markdown
![Описание картинки](assets/primer.png)
```
Картинку можно масштабировать (включено расширение `attr_list`):
```markdown
![Описание](assets/primer.png){ width="500" }
```
<!-- Когда добавите файл docs/assets/primer.png, раскомментируйте строку ниже:
![Пример скриншота](assets/primer.png){ width="600" }
-->
## Подсказки и предупреждения
!!! note "Заметка"
Используйте блоки-заметки для важной информации.
!!! warning "Внимание"
Так выделяются предупреждения.
??? info "Нажмите, чтобы развернуть"
Скрытый блок — удобно для длинных пояснений или частых вопросов.
## Сочетания клавиш
Скопировать: ++ctrl+c++ &nbsp;&nbsp; Вставить: ++ctrl+v++
## Чек-лист
- [x] Открыть приложение
- [ ] Выполнить настройку
- [ ] Проверить результат
+31
View File
@@ -0,0 +1,31 @@
# Serves the static site built by Zensical (./public) for client 05.
# Mounted at /etc/nginx/conf.d/default.conf inside nginx:alpine.
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Zensical uses directory-style URLs (/page/ -> /page/index.html).
# Unknown paths fall through to the generated 404 page.
error_page 404 /404.html;
location / {
try_files $uri $uri/ =404;
}
# Long-cache fingerprinted theme assets; keep HTML uncached so content
# updates show up immediately after a rebuild.
location /assets/ {
expires 7d;
add_header Cache-Control "public";
}
location = /404.html {
internal;
}
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
}
+85
View File
@@ -0,0 +1,85 @@
# Zensical configuration — client 05 (05-osk) instructions site.
# Docs: https://zensical.org/docs/setup/basics/
# Build: `zensical build --clean` -> outputs static site to ./public
[project]
site_name = "ОСК — Инструкции"
site_description = "Инструкции для пользователей (клиент 05)."
site_author = "ОСК"
site_url = "https://help.osk.team/"
copyright = """
Copyright &copy; 2026 ОСК
"""
# Explicit navigation. Add a line per Markdown page you create under docs/.
# Remove this block to let Zensical derive the nav from the folder structure.
nav = [
{ "Главная" = "index.md" },
{ "Пример инструкции" = "primer-instrukcii.md" },
]
[project.theme]
language = "ru"
features = [
"announce.dismiss",
"content.code.annotate",
"content.code.copy",
"content.tooltips",
"navigation.footer",
"navigation.indexes",
"navigation.instant",
"navigation.instant.prefetch",
"navigation.path",
"navigation.sections",
"navigation.top",
"navigation.tracking",
"search.highlight",
]
# Optional: drop a logo at docs/assets/logo.png and uncomment.
#logo = "assets/logo.png"
[[project.theme.palette]]
scheme = "default"
toggle.icon = "lucide/sun"
toggle.name = "Тёмная тема"
[[project.theme.palette]]
scheme = "slate"
toggle.icon = "lucide/moon"
toggle.name = "Светлая тема"
# ----------------------------------------------------------------------------
# Markdown extensions — enables admonitions, image attributes (sizing/align),
# task lists, code highlighting, tabs, etc. Kept from Zensical's defaults.
# ----------------------------------------------------------------------------
[project.markdown_extensions.abbr]
[project.markdown_extensions.admonition]
[project.markdown_extensions.attr_list]
[project.markdown_extensions.def_list]
[project.markdown_extensions.footnotes]
[project.markdown_extensions.md_in_html]
[project.markdown_extensions.toc]
permalink = true
[project.markdown_extensions.pymdownx.betterem]
[project.markdown_extensions.pymdownx.caret]
[project.markdown_extensions.pymdownx.details]
[project.markdown_extensions.pymdownx.emoji]
emoji_generator = "zensical.extensions.emoji.to_svg"
emoji_index = "zensical.extensions.emoji.twemoji"
[project.markdown_extensions.pymdownx.highlight]
anchor_linenums = true
line_spans = "__span"
pygments_lang_class = true
[project.markdown_extensions.pymdownx.inlinehilite]
[project.markdown_extensions.pymdownx.keys]
[project.markdown_extensions.pymdownx.magiclink]
[project.markdown_extensions.pymdownx.mark]
[project.markdown_extensions.pymdownx.smartsymbols]
[project.markdown_extensions.pymdownx.superfences]
[project.markdown_extensions.pymdownx.tabbed]
alternate_style = true
combine_header_slug = true
[project.markdown_extensions.pymdownx.tasklist]
custom_checkbox = true
[project.markdown_extensions.pymdownx.tilde]