Documentation Restructure
This commit is contained in:
34
services/documentation/docusaurus.md
Normal file
34
services/documentation/docusaurus.md
Normal file
@@ -0,0 +1,34 @@
|
||||
**Purpose**: An optimized site generator in React. Docusaurus helps you to move fast and write content. Build documentation websites, blogs, marketing pages, and more.
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
docusaurus:
|
||||
image: awesometic/docusaurus
|
||||
container_name: docusaurus
|
||||
environment:
|
||||
- TARGET_UID=1000
|
||||
- TARGET_GID=1000
|
||||
- AUTO_UPDATE=true
|
||||
- WEBSITE_NAME=docusaurus
|
||||
- TEMPLATE=classic
|
||||
- TZ=America/Denver
|
||||
restart: always
|
||||
volumes:
|
||||
- /srv/containers/docusaurus:/docusaurus
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
networks:
|
||||
docker_network:
|
||||
ipv4_address: 192.168.5.72
|
||||
networks:
|
||||
docker_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
```yaml title=".env"
|
||||
Not Applicable
|
||||
```
|
||||
180
services/documentation/material-mkdocs.md
Normal file
180
services/documentation/material-mkdocs.md
Normal file
@@ -0,0 +1,180 @@
|
||||
**Purpose**: Documentation that simply works. Write your documentation in Markdown and create a professional static site for your Open Source or commercial project in minutes – searchable, customizable, more than 60 languages, for all devices.
|
||||
|
||||
## Deploy Material MKDocs
|
||||
```yaml title="docker-compose.yml"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mkdocs:
|
||||
container_name: mkdocs
|
||||
image: squidfunk/mkdocs-material
|
||||
restart: always
|
||||
environment:
|
||||
- TZ=America/Denver
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- /srv/containers/material-mkdocs/docs:/docs
|
||||
networks:
|
||||
docker_network:
|
||||
ipv4_address: 192.168.5.76
|
||||
networks:
|
||||
docker_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
```yaml title=".env"
|
||||
N/A
|
||||
```
|
||||
|
||||
## Config Example
|
||||
When you deploy MKDocs, you will need to give it a configuration to tell MKDocs how to structure itself. The configuration below is what I used in my deployment. This file is one folder level higher than the `/docs` folder that holds the documentation of the website.
|
||||
```yaml title="/srv/containers/material-mkdocs/docs/mkdocs.yml"
|
||||
# Project information
|
||||
site_name: Bunny Lab
|
||||
site_url: https://kb.bunny-lab.io
|
||||
site_author: Nicole Rappe
|
||||
site_description: >-
|
||||
Server, Script, Workflow, and Networking Documentation
|
||||
repo_url: https://git.bunny-lab.io/bunny-lab/docs
|
||||
repo_name: bunny-lab/docs
|
||||
edit_uri: _edit/main/
|
||||
|
||||
# Configuration
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: material/overrides
|
||||
features:
|
||||
- announce.dismiss
|
||||
- content.action.edit
|
||||
# - content.action.view
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.code.select
|
||||
- content.tabs.link
|
||||
- content.tooltips
|
||||
# - header.autohide
|
||||
# - navigation.expand
|
||||
# - navigation.footer
|
||||
- navigation.indexes
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.instant.progress
|
||||
- navigation.prune
|
||||
- navigation.path
|
||||
# - navigation.sections
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.top
|
||||
- navigation.tracking
|
||||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
- toc.follow
|
||||
# - toc.integrate ## If this is enabled, the TOC will appear on the left navigation menu.
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/link
|
||||
name: Switch to light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: deep purple
|
||||
accent: deep purple
|
||||
toggle:
|
||||
icon: material/toggle-switch
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
accent: deep purple
|
||||
toggle:
|
||||
icon: material/toggle-switch-off
|
||||
name: Switch to system preference
|
||||
font:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
favicon: assets/favicon.png
|
||||
icon:
|
||||
logo: logo
|
||||
|
||||
# Plugins
|
||||
plugins:
|
||||
- search:
|
||||
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
||||
- minify:
|
||||
minify_html: true
|
||||
- blog
|
||||
- tags
|
||||
|
||||
# Hooks
|
||||
hooks:
|
||||
- material/overrides/hooks/shortcodes.py
|
||||
- material/overrides/hooks/translations.py
|
||||
|
||||
# Additional configuration
|
||||
extra:
|
||||
status:
|
||||
new: Recently added
|
||||
deprecated: Deprecated
|
||||
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
# Extensions
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
- admonition
|
||||
- attr_list
|
||||
- def_list
|
||||
- footnotes
|
||||
- md_in_html
|
||||
- toc:
|
||||
permalink: true
|
||||
toc_depth: 3
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.caret
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.keys
|
||||
- pymdownx.magiclink:
|
||||
normalize_issue_symbols: true
|
||||
repo_url_shorthand: true
|
||||
user: squidfunk
|
||||
repo: mkdocs-material
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.snippets:
|
||||
auto_append:
|
||||
- includes/mkdocs.md
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
combine_header_slug: true
|
||||
slugify: !!python/object/apply:pymdownx.slugs.slugify
|
||||
kwds:
|
||||
case: lower
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tilde
|
||||
```
|
||||
|
||||
## Cleaning up
|
||||
When the server is deployed, it will come with a bunch of unnecessary documentation that tells you how to use it. You will want to go into the `/docs` folder, and delete everything except `assets/favicon.png`, `schema.json`, and `/schema`. These files are necessary to allow MKDocs to automatically detect and structure the documentation based on the file folder structure under `/docs`.
|
||||
|
||||
## Hotloading Bug Workaround
|
||||
There is a [known bug](https://github.com/mkdocs/mkdocs/issues/4055) with the most recent version of Material MKDocs (as of writing) that causes it to not hotload changes immediately. This can be fixed by entering a shell in the docker container using `/bin/sh` then running the following command to downgrade the python "click" package: `pip install click==8.2.1`. After running the command, restart the container and hotloaded changes should start working again. You will have to run this command every time you re-deploy Material MKDocs until the issue is resolved officially.
|
||||
325
services/documentation/zensical.md
Normal file
325
services/documentation/zensical.md
Normal file
@@ -0,0 +1,325 @@
|
||||
## Purpose
|
||||
After many years of using Material for MKDocs and it being updated with new features and security updates, it finally reached EOL around the end of 2025. The project maintainers started pivoting to a new successor called [Zensical](https://zensical.org/docs/get-started/). This document outlines my particular process for setting up a standalone documentation server within a virtual machine.
|
||||
|
||||
!!! info "Assumptions"
|
||||
It is assumed that you are deploying this server into `Ubuntu Server 24.04.2 LTS (Minimal)`. It is also assumed that you are running every command as a user with superuser privileges (e.g. `root`).
|
||||
|
||||
You are generally safe to have a GuestVM with 16GB for the virtual disk, and expand it over-time based on your needs. CPU count and RAM allocation can also be extremely low based on your preferences, since this is simply a static page website at the end of the day.
|
||||
|
||||
## Architectural Overview
|
||||
It is useful to understand the flow of data and how everything inter-connects, so I have provided a sequence diagram that you can follow below:
|
||||
|
||||
``` mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor Author as Doc Author
|
||||
participant Gitea as Gitea (Repo + Actions)
|
||||
participant Runner as Act Runner
|
||||
participant Zensical as Zensical Server (watch + build)
|
||||
participant NGINX as NGINX (serves static site)
|
||||
|
||||
Author->>Gitea: Push to main
|
||||
Gitea-->>Runner: Trigger workflow job
|
||||
Runner->>Zensical: rsync docs → /srv/zensical/docs
|
||||
Zensical-->>Zensical: Watch detects change
|
||||
Zensical->>Zensical: Rebuild site → /srv/zensical/site
|
||||
NGINX-->>NGINX: Serve files from /srv/zensical/site
|
||||
```
|
||||
|
||||
## Setup Python Environment
|
||||
The first thing we need to do is install the necessary python packages and install the zensical software stack inside of it.
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install -y nano python3 python3.12-venv
|
||||
mkdir -p /srv/zensical
|
||||
cd /srv/zensical
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install zensical
|
||||
zensical new .
|
||||
deactivate
|
||||
|
||||
# Remove Placeholder Example Docs
|
||||
rm -rf /srv/zensical/docs/{*,.*}
|
||||
```
|
||||
|
||||
## Zensical
|
||||
### Configure Settings
|
||||
Now we want to set some sensible defaults for Zensical to style it to look as close to Material for MKDocs as possible.
|
||||
|
||||
```sh
|
||||
sudo tee /srv/zensical/zensical.toml > /dev/null <<'EOF'
|
||||
[project]
|
||||
site_name = "Bunny Lab"
|
||||
site_description = "Server, Script, Workflow, and Networking Documentation"
|
||||
site_author = "Nicole Rappe"
|
||||
site_url = "https://kb.bunny-lab.io/"
|
||||
repo_url = "https://git.bunny-lab.io/bunny-lab/docs"
|
||||
repo_name = "bunny-lab/docs"
|
||||
edit_uri = "_edit/main/"
|
||||
|
||||
[project.theme]
|
||||
variant = "classic"
|
||||
language = "en"
|
||||
features = [
|
||||
"announce.dismiss",
|
||||
"content.action.edit",
|
||||
"content.code.annotate",
|
||||
"content.code.copy",
|
||||
"content.code.select",
|
||||
"content.footnote.tooltips",
|
||||
"content.tabs.link",
|
||||
"content.tooltips",
|
||||
"navigation.indexes",
|
||||
"navigation.instant",
|
||||
"navigation.instant.prefetch",
|
||||
"navigation.instant.progress",
|
||||
"navigation.path",
|
||||
"navigation.tabs",
|
||||
"navigation.tabs.sticky",
|
||||
"navigation.top",
|
||||
"navigation.tracking",
|
||||
"search.highlight",
|
||||
]
|
||||
|
||||
[[project.theme.palette]]
|
||||
scheme = "default"
|
||||
toggle.icon = "lucide/sun"
|
||||
toggle.name = "Switch to dark mode"
|
||||
|
||||
[[project.theme.palette]]
|
||||
scheme = "slate"
|
||||
toggle.icon = "lucide/moon"
|
||||
toggle.name = "Switch to light mode"
|
||||
|
||||
EOF
|
||||
```
|
||||
|
||||
### Create Watchdog Service
|
||||
Since NGINX has taken over hosting the webpages, this does not need to be accessible from other servers, only NGINX itself which runs on the same host as Zensical. We only want to use the `zensical serve` command to keep a watchdog on the documentation folder and automatically rebuild the static site content when changes are detected. These changes are then served by NGINX's webserver.
|
||||
|
||||
```sh
|
||||
# Create Service User, Assign Access, and Lockdown Zensical Data
|
||||
sudo useradd --system --home /srv/zensical --shell /usr/sbin/nologin zensical || true
|
||||
sudo chown -R zensical:zensical /srv/zensical
|
||||
sudo find /srv/zensical -type d -exec chmod 2775 {} \;
|
||||
sudo find /srv/zensical -type f -exec chmod 664 {} \; # This step likes to take a while, sometimes up to a minute.
|
||||
```
|
||||
|
||||
```sh
|
||||
# Make Zensical Binary Executable for Service
|
||||
sudo chmod +x /srv/zensical/.venv/bin/zensical
|
||||
|
||||
# Add Additional User(s) to Folder for Extra Access (Such as Doc Runners)
|
||||
sudo usermod -aG zensical nicole
|
||||
|
||||
# Create Service
|
||||
sudo tee /etc/systemd/system/zensical-watchdog.service > /dev/null <<'EOF'
|
||||
[Unit]
|
||||
Description=Zensical Document Changes Watchdog (zensical serve)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=zensical
|
||||
Group=zensical
|
||||
WorkingDirectory=/srv/zensical
|
||||
|
||||
# Run the venv binary directly; no activation needed
|
||||
ExecStart=/srv/zensical/.venv/bin/zensical serve
|
||||
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Start & Enable Automatic Startup of Service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now zensical-watchdog.service
|
||||
```
|
||||
|
||||
## NGINX Webserver
|
||||
We need to deploy NGINX as a webserver, because when using reverse proxies like Traefik, it seems to not get along with Zensical at all. Attempts to resolve this all failed, so putting the statically-built copies of site data that Zensical generates into NGINX's root directory is the second-best solution I came up with. Traefik can be reasonably expected to behave when interacting with NGINX versus Zensical's built-in webserver.
|
||||
|
||||
```sh
|
||||
sudo apt install -y nginx
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
sudo tee /etc/nginx/sites-available/zensical.conf > /dev/null <<'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
root /srv/zensical/site;
|
||||
index index.html;
|
||||
|
||||
# Primary document handling
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Static asset caching (safe for docs)
|
||||
location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, max-age=604800, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Prevent access to source or metadata
|
||||
location ~* \.(toml|md)$ {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -s /etc/nginx/sites-available/zensical.conf /etc/nginx/sites-enabled/zensical.conf
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
sudo systemctl enable nginx
|
||||
```
|
||||
|
||||
## Gitea ACT Runner
|
||||
Now is time for the arguably most-important stage of deployment, which is setting up a [Gitea Act Runner](https://docs.gitea.com/usage/actions/act-runner). This is how document changes in a Gitea repository will propagate automatically into Zensical's `/srv/zensical/docs` folder.
|
||||
|
||||
```sh
|
||||
# Install Dependencies
|
||||
sudo apt install -y nodejs npm git rsync curl
|
||||
|
||||
# Create dedicated Gitea runner service account
|
||||
sudo useradd --system --create-home --home /var/lib/gitea_runner --shell /usr/sbin/nologin gitearunner || true
|
||||
|
||||
# Allow the runner to write documentation changes
|
||||
sudo usermod -aG zensical gitearunner
|
||||
|
||||
# Download Newest Gitea Runner Binary (https://gitea.com/gitea/act_runner/releases)
|
||||
cd /tmp
|
||||
wget https://gitea.com/gitea/act_runner/releases/download/v0.2.13/act_runner-0.2.13-linux-amd64
|
||||
sudo install -m 0755 act_runner-0.2.13-linux-amd64 /usr/local/bin/gitea_runner
|
||||
gitea_runner --version
|
||||
|
||||
# Generate Gitea Runner Configuration
|
||||
sudo mkdir -p /etc/gitea_runner
|
||||
sudo chown gitearunner:gitearunner /etc/gitea_runner
|
||||
sudo -u gitearunner gitea_runner generate-config > /etc/gitea_runner/config.yaml
|
||||
```
|
||||
|
||||
### Configure Registration Token
|
||||
- Navigate to: "**<Gitea Repo> > Settings > Actions > Runners**"
|
||||
- If you don't see this, it needs to be enabled. Navigate to: "**<Gitea Repo> > Settings > "Enable Repository Actions: Enabled" > Update Settings**"
|
||||
- Click the "**Create New Runner**" button on the top-right of the page and copy the registration token somewhere temporarily.
|
||||
- Navigate back to the GuestVM running Zensical and run the following commands.
|
||||
|
||||
```sh
|
||||
# Start Token Registration Process
|
||||
sudo -u gitearunner env HOME=/var/lib/gitea_runner /usr/local/bin/gitea_runner register --config /etc/gitea_runner/config.yaml
|
||||
|
||||
# Gitea Instance URL: https://git.bunny-lab.io
|
||||
# Gitea Runner Token: <Gitea-Runner-Token>
|
||||
# Runner Name: zensical-docs-runner
|
||||
|
||||
# Move Runner Config to Correct Location & Configure Permissions
|
||||
sudo mv /tmp/.runner /var/lib/gitea_runner/.runner
|
||||
sudo chown gitearunner:gitearunner /var/lib/gitea_runner/.runner
|
||||
sudo chmod 600 /var/lib/gitea_runner/.runner
|
||||
```
|
||||
|
||||
### Create Service
|
||||
Now we need to configure the Gitea runner to start automatically via a service just like the Zensical Watchdog service.
|
||||
|
||||
```sh
|
||||
# Create Gitea Runner Service
|
||||
sudo tee /etc/systemd/system/gitea-runner.service > /dev/null <<'EOF'
|
||||
[Unit]
|
||||
Description=Gitea Actions Runner (gitea_runner)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=HOME=/var/lib/gitea_runner
|
||||
User=gitearunner
|
||||
Group=gitearunner
|
||||
WorkingDirectory=/var/lib/gitea_runner
|
||||
ExecStart=/usr/local/bin/gitea_runner daemon --config /etc/gitea_runner/config.yaml
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Remove Container-Based Configurations to Force Runner to Run in Host Mode
|
||||
sudo sed -i \
|
||||
'/^[[:space:]]*labels:/,/^[[:space:]]*cache:/{
|
||||
/^[[:space:]]*labels:/c\ labels:\n - "zensical-host:host"
|
||||
/^[[:space:]]*cache:/!d
|
||||
}' \
|
||||
/etc/gitea_runner/config.yaml
|
||||
|
||||
# Enable and Start the Service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gitea-runner.service
|
||||
```
|
||||
|
||||
### Repository Workflow
|
||||
Place the following file into your documentation repository at the given location and this will enable the runner to execute when changes happen to the repository data.
|
||||
|
||||
```yaml title="gitea/workflows/gitops-automatic-deployment.yml"
|
||||
name: GitOps Automatic Documentation Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
zensical_deploy:
|
||||
name: Sync Docs to https://kb.bunny-lab.io
|
||||
runs-on: zensical-host
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Sync repository into /srv/zensical/docs
|
||||
run: |
|
||||
rsync -rlD --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='.gitea/' \
|
||||
--exclude='assets/' \
|
||||
--exclude='schema/' \
|
||||
--exclude='stylesheets/' \
|
||||
--exclude='schema.json' \
|
||||
--chmod=D2775,F664 \
|
||||
. /srv/zensical/docs/
|
||||
|
||||
- name: Notify via NTFY
|
||||
if: always()
|
||||
run: |
|
||||
curl -d "https://kb.bunny-lab.io - Zensical job status: ${{ job.status }}" https://ntfy.bunny-lab.io/gitea-runners
|
||||
```
|
||||
|
||||
## Traefik Reverse Proxy
|
||||
It is assumed that you use a [Traefik](../edge/traefik.md) reverse proxy and are configured to use [dynamic configuration files](../edge/traefik.md#dynamic-configuration-files). Add the file below to expose the Zensical service to the rest of the world.
|
||||
|
||||
```yaml title="kb.bunny-lab.io.yml"
|
||||
http:
|
||||
routers:
|
||||
kb:
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
service: kb
|
||||
rule: Host(`kb.bunny-lab.io`)
|
||||
|
||||
services:
|
||||
kb:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://192.168.3.8:80
|
||||
passHostHeader: true
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user