1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
#!/usr/bin/env bash
# setup.sh — Bootstrap a self-hosted Docsify site (static files; serve with
# any web server — Apache, nginx, Caddy, …).
#
# Run this script from your web server's document root:
# cd /path/to/docroot
# bash setup.sh
#
# Downloads Docsify core, the search plugin, the Vue theme, the copy-to-clipboard
# plugin, extra Prism languages (bash, yaml, python, r), KaTeX math, Mermaid diagrams,
# and UX plugins (alerts, tabs, pagination, image zoom, collapsible sidebar) —
# all served locally, no external CDN dependencies at runtime. Versions are pinned and
# a SHA-256 manifest (assets/SHA256SUMS) is written for reproducible, verifiable rebuilds.
#
# Re-running in an existing site is safe: it refreshes the assets and regenerates
# index.html, but never overwrites Markdown files (README.md, _sidebar.md, and any
# pages you have added) that already exist.
set -euo pipefail
# ── Configuration ───────────────────────────────────────────────────────────────
# All versions are pinned exactly for reproducible, CDN-independent rebuilds. Bump a
# version here, re-run, and commit the refreshed assets/ + assets/SHA256SUMS.
DOCSIFY_VERSION="4.13.1"
PRISM_VERSION="1.30.0"
COPY_CODE_VERSION="3.0.2"
FLEXIBLE_ALERTS_VERSION="1.3.0"
TABS_VERSION="1.6.3"
PAGINATION_VERSION="2.10.1"
MEDIUM_ZOOM_VERSION="1.1.0"
SIDEBAR_COLLAPSE_VERSION="1.3.5"
MERMAID_VERSION="11.15.0"
DOCSIFY_MERMAID_VERSION="2.0.1"
# Math plugin and its KaTeX renderer are coupled: the standalone KaTeX stylesheet/font
# version (KATEX_VERSION) MUST match the renderer bundled inside this docsify-katex
# release. docsify-katex 1.4.4 bundles KaTeX 0.11.1 (the 2.x line is broken). Bump the
# two together.
DOCSIFY_KATEX_VERSION="1.4.4"
KATEX_VERSION="0.11.1"
# ── Argument check ──────────────────────────────────────────────────────────────
if [[ $# -gt 0 ]]; then
echo "This script takes no options (got: $*)." >&2
echo "Usage: bash setup.sh" >&2
exit 1
fi
# ── Output helpers ──────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
step() { echo -e "\n${BLUE}${BOLD}==> $1${NC}"; }
ok() { echo -e " ${GREEN}✓${NC} $1"; }
skip() { echo -e " ${YELLOW}•${NC} $1"; }
die() { echo -e " ${RED}✗${NC} $1" >&2; exit 1; }
# ── Preflight checks ────────────────────────────────────────────────────────────
step "Preflight checks"
command -v curl &>/dev/null \
|| die "curl is required but not found. Install curl and try again."
ok "curl $(curl --version | head -1 | awk '{print $2}')"
if [[ -f index.html ]]; then
echo -e " ${RED}!${NC} index.html already exists in: $(pwd)"
echo -e " This re-downloads assets and regenerates index.html (edits to"
echo -e " index.html will be lost). Existing Markdown files are left untouched."
read -r -p " Continue? [y/N] " reply || reply="" # EOF (non-interactive) -> treat as "no"
[[ "${reply,,}" == "y" ]] || { echo "Aborted."; exit 0; }
fi
echo -e " Working in: $(pwd)"
# ── Step 1: Directories ─────────────────────────────────────────────────────────
step "Step 1 — Creating directory structure"
mkdir -p assets/js assets/css assets/css/fonts
ok "assets/js/"
ok "assets/css/"
# ── Step 2: Download Docsify assets ─────────────────────────────────────────────
step "Step 2 — Downloading Docsify assets"
CDN="https://cdn.jsdelivr.net/npm"
fetch() {
local url="$1" dest="$2"
curl -sL --fail "$url" -o "$dest" || die "Failed to download: $url"
ok "$dest"
}
# Core Docsify, search plugin, and theme
fetch "${CDN}/docsify@${DOCSIFY_VERSION}/lib/docsify.min.js" assets/js/docsify.min.js
fetch "${CDN}/docsify@${DOCSIFY_VERSION}/lib/plugins/search.min.js" assets/js/search.min.js
fetch "${CDN}/docsify@${DOCSIFY_VERSION}/themes/vue.css" assets/css/vue.css
# Copy-to-clipboard plugin (injects its own CSS — no separate stylesheet needed)
fetch "${CDN}/docsify-copy-code@${COPY_CODE_VERSION}/dist/docsify-copy-code.min.js" assets/js/docsify-copy-code.min.js
# Extra Prism languages. Docsify already bundles Prism core plus html/css/clike/
# javascript, so we only add the languages it does NOT ship. Never download
# prism-core here — a standalone core replaces Docsify's Prism and silently breaks
# highlighting for every language loaded after it.
fetch "${CDN}/prismjs@${PRISM_VERSION}/components/prism-bash.min.js" assets/js/prism-bash.min.js
fetch "${CDN}/prismjs@${PRISM_VERSION}/components/prism-yaml.min.js" assets/js/prism-yaml.min.js
fetch "${CDN}/prismjs@${PRISM_VERSION}/components/prism-python.min.js" assets/js/prism-python.min.js
fetch "${CDN}/prismjs@${PRISM_VERSION}/components/prism-r.min.js" assets/js/prism-r.min.js
# Math: docsify-katex bundles the KaTeX renderer (version coupling is documented in the
# Configuration block above). KaTeX's stylesheet loads its fonts from ./fonts/ beside it,
# so the woff2 files go in assets/css/fonts/. Stylesheet + fonts are pinned to the bundled
# KaTeX (KATEX_VERSION).
fetch "${CDN}/docsify-katex@${DOCSIFY_KATEX_VERSION}/dist/docsify-katex.js" assets/js/docsify-katex.js
fetch "${CDN}/katex@${KATEX_VERSION}/dist/katex.min.css" assets/css/katex.min.css
# Read the woff2 font list out of the stylesheet, then download each. Guard against an
# empty match: a future KaTeX bump could change the CSS format, which would otherwise
# silently ship a site whose math renders without fonts.
mapfile -t katex_fonts < <(grep -oE 'fonts/KaTeX_[^)]+\.woff2' assets/css/katex.min.css | sort -u)
[[ ${#katex_fonts[@]} -gt 0 ]] \
|| die "No KaTeX woff2 fonts found in katex.min.css; the CSS format may have changed for katex@${KATEX_VERSION}."
for f in "${katex_fonts[@]}"; do
curl -sL --fail "${CDN}/katex@${KATEX_VERSION}/dist/$f" -o "assets/css/$f" \
|| die "Failed to download: $f"
done
ok "assets/css/fonts/ (${#katex_fonts[@]} KaTeX woff2 files)"
# UX plugins — each loads after docsify; alerts/tabs/pagination inject their own CSS.
fetch "${CDN}/docsify-plugin-flexible-alerts@${FLEXIBLE_ALERTS_VERSION}/dist/docsify-plugin-flexible-alerts.min.js" assets/js/docsify-plugin-flexible-alerts.min.js
fetch "${CDN}/docsify-tabs@${TABS_VERSION}/dist/docsify-tabs.min.js" assets/js/docsify-tabs.min.js
fetch "${CDN}/docsify-pagination@${PAGINATION_VERSION}/dist/docsify-pagination.min.js" assets/js/docsify-pagination.min.js
# Image zoom: medium-zoom is a generic library (a hook in index.html applies it) + its CSS
fetch "${CDN}/medium-zoom@${MEDIUM_ZOOM_VERSION}/dist/medium-zoom.min.js" assets/js/medium-zoom.min.js
fetch "${CDN}/medium-zoom@${MEDIUM_ZOOM_VERSION}/dist/style.css" assets/css/medium-zoom.css
# Collapsible sidebar — needs its own stylesheet
fetch "${CDN}/docsify-sidebar-collapse@${SIDEBAR_COLLAPSE_VERSION}/dist/docsify-sidebar-collapse.min.js" assets/js/docsify-sidebar-collapse.min.js
fetch "${CDN}/docsify-sidebar-collapse@${SIDEBAR_COLLAPSE_VERSION}/dist/sidebar.min.css" assets/css/sidebar.min.css
# Diagrams: Mermaid (self-contained, ~3 MB) + docsify-mermaid, which turns ```mermaid
# fenced blocks into diagrams (it uses Mermaid v11's run() API).
fetch "${CDN}/mermaid@${MERMAID_VERSION}/dist/mermaid.min.js" assets/js/mermaid.min.js
fetch "${CDN}/docsify-mermaid@${DOCSIFY_MERMAID_VERSION}/dist/docsify-mermaid.js" assets/js/docsify-mermaid.js
# Record SHA-256 checksums of every downloaded asset. Commit assets/ together with this
# manifest to rebuild without jsDelivr and to verify integrity later with:
# (cd assets && sha256sum -c SHA256SUMS)
( cd assets && find . -type f ! -name SHA256SUMS \( -name '*.js' -o -name '*.css' -o -name '*.woff2' \) \
-print0 | sort -z | xargs -0 sha256sum > SHA256SUMS )
ok "assets/SHA256SUMS ($(wc -l < assets/SHA256SUMS) files)"
# ── Step 3: index.html ──────────────────────────────────────────────────────────
step "Step 3 — Creating index.html"
# The heredoc delimiter is single-quoted ('HTML') so the shell does not expand
# $docsify inside the template.
cat > index.html << 'HTML'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Docs</title>
<link rel="stylesheet" href="assets/css/vue.css">
<link rel="stylesheet" href="assets/css/katex.min.css">
<link rel="stylesheet" href="assets/css/sidebar.min.css">
<link rel="stylesheet" href="assets/css/medium-zoom.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'My Docs',
loadSidebar: true, // enables _sidebar.md
subMaxLevel: 2, // auto-generate H2 entries in sidebar
search: 'auto', // enables the search plugin
plugins: [
// medium-zoom: click an image in the content to enlarge it
function (hook) {
hook.doneEach(function () {
if (window.mediumZoom) mediumZoom('.markdown-section img');
});
}
]
}
</script>
<script src="assets/js/docsify.min.js"></script>
<script src="assets/js/search.min.js"></script>
<script src="assets/js/docsify-copy-code.min.js"></script>
<script src="assets/js/docsify-katex.js"></script>
<!-- Extra Prism languages — Docsify bundles Prism core + html/css/js. Load after docsify. -->
<script src="assets/js/prism-bash.min.js"></script>
<script src="assets/js/prism-yaml.min.js"></script>
<script src="assets/js/prism-python.min.js"></script>
<script src="assets/js/prism-r.min.js"></script>
<!-- UX plugins (callout boxes, tabs, prev/next pagination, image zoom, collapsible sidebar) -->
<script src="assets/js/docsify-plugin-flexible-alerts.min.js"></script>
<script src="assets/js/docsify-tabs.min.js"></script>
<script src="assets/js/docsify-pagination.min.js"></script>
<script src="assets/js/medium-zoom.min.js"></script>
<script src="assets/js/docsify-sidebar-collapse.min.js"></script>
<!-- Diagrams: Mermaid + docsify integration (disable Mermaid's own auto-run) -->
<script src="assets/js/mermaid.min.js"></script>
<script src="assets/js/docsify-mermaid.js"></script>
<script>mermaid.initialize({ startOnLoad: false });</script>
</body>
</html>
HTML
ok "index.html"
# ── Step 4: Boilerplate content ─────────────────────────────────────────────────
step "Step 4 — Boilerplate content (created only if missing)"
# Write the heredoc on stdin to $1, but never clobber an existing file — so
# re-running refreshes the assets and index.html without touching your content.
write_boilerplate() {
local dest="$1"
local content
content="$(cat)"
if [[ -e "$dest" ]]; then
skip "$dest (exists — left unchanged)"
else
printf '%s\n' "$content" > "$dest"
ok "$dest"
fi
}
write_boilerplate README.md << 'EOF'
# Welcome
This is the home page of your documentation site.
Edit `README.md` to replace this content.
## Quick Links
- [Getting Started](getting-started.md)
EOF
write_boilerplate _sidebar.md << 'EOF'
- [Home](/)
- [Getting Started](getting-started.md)
EOF
write_boilerplate getting-started.md << 'EOF'
# Getting Started
This is an example page. Replace this content with your own documentation.
## Section One
Write your content here using standard Markdown.
## Section Two
Docsify will automatically generate sidebar anchors for H2 headings
when `subMaxLevel: 2` is set in `index.html`.
EOF
# ── Step 5: Permissions ─────────────────────────────────────────────────────────
step "Step 5 — Setting file permissions"
# Prune dotfiles/dotdirs so a docroot that is a Git repo keeps its .git/ perms intact.
find . -path './.*' -prune -o -type f -exec chmod 644 {} \;
find . -path './.*' -prune -o -type d -exec chmod 755 {} \;
ok "Files: 644 | Directories: 755 (dotfiles left untouched)"
# ── Verify ───────────────────────────────────────────────────────────────────────
step "Verifying file layout"
find . -not -path '*/\.*' -not -path './assets/css/fonts*' | sort
echo "./assets/css/fonts/ ($(ls -1 assets/css/fonts 2>/dev/null | wc -l) KaTeX woff2 files)"
# ── Done ─────────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD}Setup complete.${NC}"
echo -e "Open your site in a browser to verify."
echo ""
echo -e "Syntax highlighting: html, css, and javascript are built into Docsify;"
echo -e "this setup adds bash, yaml, python, and r. To add more languages, download"
echo -e "additional prism-<lang>.min.js files into assets/js/ and add their <script>"
echo -e "tags to index.html. Copy-to-clipboard buttons are enabled on all code blocks."
echo -e "LaTeX math renders via KaTeX (inline \$...\$ and block \$\$...\$\$)."
echo 'Mermaid diagrams render from ```mermaid fenced blocks.'
echo 'UX: callout boxes (> [!NOTE]), tabs, prev/next pagination, image zoom, and a'
echo 'collapsible sidebar are enabled.'
echo ""
echo -e "Asset versions are pinned and checksummed in assets/SHA256SUMS"
echo -e "(verify any time with: cd assets && sha256sum -c SHA256SUMS)."
echo -e "Commit assets/ to Git to rebuild without depending on jsDelivr."
|