mdbook
与 Nginx 下的内容发布 Web 站点管理
mdbook
是 Rust 实现的一个类似于 gitbook
的文档服务器,通过使用 MarkDown 编写文档,mdbook
可将其构建为 HTML、PDF 目标,并可运行 HTTP 服务器,在线提供文档。咱们使用 Nginx 做 mdbook
的反向代理,带来高并发、SSL/TLS 等额外功能。
以下是在建立此种文档服务器时,用到的一些配置与脚本。
include
自己的一些行
include
指令可以重用 Markdown 文档中的一些行。在 linux.md
中写下双花括号括起来的 #include ./linux.md:119:191
便可以引用 linux.md
本身的 119
到 191
行。
服务器管理脚本
文件:srv_incl.sh
INFO_CLR="\033[1;90;1;93m"
SUCESS_CLR="\033[0;90;2;92m"
END_CLR="\033[0m"
ALERT_CLR="\033[5;47;1;31m"
LOG_FILE="${HOME}/log/srv_mgt.log"
declare -A dirs
dirs["rust-lang"]="rust-lang-zh_CN"
dirs["java"]="learningJava"
dirs["ccna60d"]="ccna60d"
dirs["ts"]="ts-learnings"
dirs["www"]="buy-me-a-coffee"
dirs["snippets"]="code_snippets"
dirs["jenkins"]="jenkins_book_zh"
dirs["hpcl"]="hpc-studies"
declare -A ports
ports["rust-lang"]="10443"
ports["java"]="10445"
ports["ccna60d"]="10444"
ports["ts"]="10447"
ports["www"]="10446"
ports["snippets"]="10448"
ports["jenkins"]="10449"
ports["hpcl"]="10450"
COMMANDS=("start" "stop" "restart" "monitor" "status")
OPTIONS=("all")
for name in ${!dirs[@]}; do OPTIONS=(${OPTIONS[@]} "${name}"); done
dir_name=`/usr/bin/dirname $LOG_FILE`
if [ ! -d "${dirname}" ]; then /usr/bin/mkdir -p $dir_name; fi
if [ ! -f "${LOG_FILE}" ]; then touch $LOG_FILE; fi
stop_srv() {
proc_num=`/usr/bin/netstat -ntlp 2> /dev/null | grep "${ports[$1]}" | wc -l`
if [ $proc_num -ne 0 ]; then
echo -e "\n\rStopping $1 ..."
kill `/usr/bin/netstat -ntlp 2> /dev/null | grep ${ports[$1]} | awk -F' ' '{print $7}' | awk -F'/' '{print $1}'`
sleep 3
fi
}
gen_sitemap() {
echo -e "\r\nGenerating $1 sitemap.xml..."
if [ ! -f "book/sitemap.xml" ]; then
mdbook-sitemap-generator -d "$1.xfoss.com" -o book/sitemap.xml
/usr/bin/sed -i '1 i\<?xml version="1.0" encoding="utf-8" ?>' book/sitemap.xml
/usr/bin/sed -i 's/\.md/\.html/g' book/sitemap.xml
/usr/bin/sed -i 's/<loc>/<loc>https:\/\//g' book/sitemap.xml
/usr/bin/sed -i 's/<urlset>/<urlset xmlns=\"http:\/\/www.sitemaps.org\/schemas\/sitemap\/0.9\">/g' book/sitemap.xml
fi
}
start_srv() {
proc_num=`/usr/bin/netstat -ntlp 2> /dev/null | grep "${ports[$1]}" | wc -l`
if [ $proc_num -ne 1 ]; then
cd "$HOME/${dirs[$1]}"
echo -e "\n\rStarting $1 ..."
mdbook serve . -p "${ports[$1]}" -n 127.0.0.1 > /dev/null 2>&1 &
fi
}
start_all() {
for name in ${!dirs[@]}; do start_srv $name; done
}
kill_all() {
echo -e "\n\rStopping all..."
/usr/bin/ps -A | grep mdbook | while read -r line; do
kill $(echo $line | awk -F' ' '{print $1}') && sleep 3
done
}
get_status() {
echo "---------------------------------------------"
echo -e " ${INFO_CLR}$1.xfoss.com${END_CLR} 状态:"
pid=$(/usr/bin/netstat -ntlp 2> /dev/null | grep ${ports[$1]} | awk -F' ' '{print $7}' | awk -F'/' '{print $1}')
re='^[0-9]+$'
if ! [[ $pid =~ $re ]] ; then echo -e "${ALERT_CLR}----- Dead !!!!!!!${END_CLR}"
else
echo -n -e "${SUCESS_CLR}"
/usr/bin/ps -p $pid -o pid,vsz=MEMORY -o etime=ELAPSED_TIME -o state=STATE,stime=START_TIME
echo -n -e "${END_CLR}"
fi
}
chk_n_restart() {
resp_code=$(/usr/bin/curl -I "https://$1.xfoss.com/sitemap.xml" 2>/dev/null | head -n 1 | cut -d$' ' -f2)
if [ $((`date +%s`-`git log -1 --format=%ct`)) -lt 900 ]; then
echo -e "\r\n$1 content updated, now restarting it..." && do_restart $1;
echo "`date` - 检查 $1 运行状态并重启服务完成" >> $LOG_FILE
exit 0
fi
if [ "$resp_code" != "200" ]; then
echo -e "\r\n$1 not running, now starting it" && start_srv $1;
fi
}
do_mon() {
cd "$HOME/${dirs[$1]}"
echo -e "\r\nTrying to checkout $1 ..."
git pull
echo "`date` - $1 git checkout 完成" >> $LOG_FILE
chk_n_restart $1
}
show_status() {
case $1 in
"all")
for name in ${!dirs[@]}; do get_status $name; done
;;
*)
get_status $1
;;
esac
}
do_start() {
case $1 in
"all")
start_all
;;
*)
start_srv $1
;;
esac
}
do_stop() {
case $1 in
"all")
kill_all
;;
*)
stop_srv $1
;;
esac
}
do_restart() {
case $1 in
"all")
kill_all && start_all
;;
*)
stop_srv $1 && start_srv $1
;;
esac
}
monitor() {
case $1 in
"all")
for name in ${!dirs[@]}; do
do_mon $name && exit 0
done
;;
*)
# do_mon $1 > /dev/null 2>&1 &
do_mon $1 && exit 0
;;
esac
}
文件:srv_mgt.sh
#!/usr/bin/bash
source $(dirname $0)/srv_incl.sh
declare -A paras
i=1;
for para in "$@"; do paras[$i]=$para && i=$((i + 1)); done
if [ $i != 3 ]; then
echo "命令行参数问题。仅支持两个参数:start/stop/restart/status, all/service_name"
echo "command: ${COMMANDS[@]}"
echo "service_name: ${!dirs[@]}" && exit 1
fi
cmd_okay=0
for cmd in ${COMMANDS[@]}; do
if [ $1 == $cmd ]; then cmd_okay=1 && break; fi
done
if [ $cmd_okay -eq 0 ]; then
echo "第一个命令行参数错误"
echo "可选参数:${COMMANDS[@]}" && exit 1
fi
option_okay=0
for opt in ${OPTIONS[@]}; do
if [ $2 == $opt ]; then option_okay=1 && break; fi
done
if [ $option_okay -eq 0 ]; then
echo "第二个命令行参数错误"
echo "可选参数:${OPTIONS[@]}" && exit 1
fi
case $1 in
"start")
do_start $2
;;
"stop")
do_stop $2
;;
"restart")
do_restart $2
;;
"status")
show_status $2
;;
"monitor")
monitor $2
;;
esac
exit 0
Nginx 配置示例
文件:/etc/nginx/nginx.conf
user unisko nginx;
worker_processes auto;
load_module modules/ngx_http_cache_purge_module.so;
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log notice;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
文件:/etc/nginx/conf.d/ccna60d.conf
upstream ccna60d { server 127.0.0.1:10444; }
server {
server_name ccna60d.xfoss.com;
location / {
proxy_pass http://ccna60d;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ccna60d.xfoss.com-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ccna60d.xfoss.com-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
一个域名下多本书的配置
此需要是通过 Nginx 反向代理中,URL 重写的设置实现的。相较于一个域名一本书的 Nginx 设置,略有差别。
upstream docs-root { server 127.0.0.1:10446; }
upstream xiaohu-zh { server 127.0.0.1:10447; }
upstream xiaohu-en { server 127.0.0.1:10448; }
server {
server_name docs.xfoss.com;
location / {
proxy_pass http://docs-root;
}
location /xiaohu/zh/ {
proxy_pass http://xiaohu-zh/;
}
location /xiaohu/en/ {
proxy_pass http://xiaohu-en/;
}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
listen 443 ssl;
ssl_certificate /etc/ssl/certs/xfoss/cert_chain.pem;
ssl_certificate_key /etc/ssl/certs/xfoss/private.key;
}
其中,差别在于这里:
location /xiaohu/zh/ {
proxy_pass http://xiaohu-zh/;
}
proxy_pass http://xiaohu-zh/
最后多了一个斜杠。
单个域名下同步单个仓库,提供多本书的操作流程
-
在代码仓库根目录下建立一本新书对应的目录。此目录下应有
src
、theme
目录及book.toml
文件等必要组成部分,所有内容的 MarkDown 文件都在src
目录下; -
修改
src_inc.sh
文件,添加上面新书的目录,以及新书的端口; -
修改 Nginx 的配置文件
/etc/nginx/config.d/docs.conf
,添加新书的upstream
设置及反向代理路径; -
运行
srv_mgt.sh start demo-book &
及systemctl restart nginx
启动新书,并重启 Nginx。