├── Makefile ├── README.md ├── build-all.sh ├── build.sh ├── description.lua ├── device.sh ├── feed.sh ├── feed.tpl ├── index.sh ├── map.sh ├── meta.lua ├── meta.sh ├── meta.tpl └── systemd ├── lehu-build.service ├── lehu-http.service ├── lehu-http.socket ├── lehu-https.socket └── lehu-quic.socket /Makefile: -------------------------------------------------------------------------------- 1 | MDs := $(shell find . -name '*.md') 2 | HTMLs := $(MDs:.md=.html) 3 | PWD := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 4 | 5 | export ROOT_DIR = $(PWD) 6 | export LUA_FILTER = $(PWD)/description.lua 7 | 8 | %.html: %.md ./head.tpl ./footer.tpl ./article.tpl 9 | pandoc -s -p --wrap=none \ 10 | --toc \ 11 | --template article.tpl \ 12 | --metadata-file=index.yaml \ 13 | --highlight-style=pygments \ 14 | --lua-filter $(LUA_FILTER) \ 15 | $< -o $@ 16 | 17 | index: ./head.tpl ./footer.tpl ./index.tpl 18 | find . -type d -exec index.sh {} \; 19 | 20 | all: index $(HTMLs) 21 | feed.sh . > feed.xml 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lehu.in 2 | 3 | 乐乎平台使用的脚本集合,用于 markdonw 转 html,生成 index.html 和 atom。 4 | 5 | ## 系统依赖 6 | 7 | - make 8 | - pandoc 9 | - jq 10 | 11 | ## 整体设计 12 | 13 | 请参考我的文章 14 | -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap exit SIGINT SIGTERM 4 | 5 | since=0 6 | events=RemoteChangeDetected,PendingDevicesChanged 7 | 8 | # 首次启动跳过已经发生的事件 9 | curl -s -H x-api-key:$api_key "$host/rest/events?timeout=0&events=$events" > /tmp/events.txt 10 | last=$(jq -r '.[]|.id' /tmp/events.txt | tail -n 1) 11 | if [[ ! -z "$last" ]]; then 12 | since=$last 13 | fi 14 | 15 | while true; do 16 | curl -s -H x-api-key:$api_key "$host/rest/events?since=$since&events=$events" > /tmp/events.txt 17 | 18 | jq -r '.[]|select(.type == "PendingDevicesChanged")|.data.added[0].deviceID|select(.!=null)' \ 19 | /tmp/events.txt | sort | uniq | \ 20 | xargs -I % device.sh % 21 | 22 | # 如果删除 md 则同步删除对应的 html 23 | jq -r '.[]|select(.type == "RemoteChangeDetected")|select(.data.action == "deleted")|"\(.data.folder)/\(.data.path)"' \ 24 | /tmp/events.txt | sort | uniq | grep -E "\.md$" | sed -E "s/\.md$/.html/" | \ 25 | xargs -I % rm -f ~/sync/% 26 | 27 | # 目前无法区分新建文件和修改文件,只能无脑触发更新索引页面 28 | jq -r '.[]|select(.type == "RemoteChangeDetected")|.data.folder' \ 29 | /tmp/events.txt | sort | uniq | \ 30 | xargs -I % build.sh % 31 | 32 | last=$(jq -r '.[]|.id' /tmp/events.txt | tail -n 1) 33 | if [[ ! -z "$last" ]]; then 34 | since=$last 35 | fi 36 | done 37 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd ~/sync/$1/ 6 | 7 | # https://stackoverflow.com/a/50673471 8 | set -o allexport 9 | source ./env 10 | 11 | make -f ~/lehu-sh/Makefile all 12 | -------------------------------------------------------------------------------- /description.lua: -------------------------------------------------------------------------------- 1 | local description = "" 2 | local runes = 0 3 | 4 | function is_ascii(char) 5 | local ascii_code = string.byte(char) 6 | return ascii_code >= 0 and ascii_code <= 127 7 | end 8 | 9 | function get_description(blocks) 10 | for _, block in ipairs(blocks) do 11 | if block.t == 'Para' then 12 | description = pandoc.utils.stringify(block) 13 | break 14 | end 15 | end 16 | 17 | for _, block in ipairs(blocks) do 18 | if block.t == 'Para' then 19 | local s = pandoc.utils.stringify(block) 20 | s,_ = s:gsub("[a-zA-Z-]+","a") 21 | s,_ = s:gsub("%s+","") 22 | 23 | runes = runes + utf8.len(s) 24 | end 25 | end 26 | 27 | return nil 28 | end 29 | 30 | -- realpath convert path with .. to absolute path 31 | -- 32 | -- "/1.md" == realpath("a.md", "1.md") 33 | -- "/1.md" == realpath("/a.md", "1.md") 34 | -- "/1.md" == realpath("/a.md", "./1.md") 35 | -- "/1.md" == realpath("/a/b.md", "../1.md") 36 | -- "/1.md" == realpath("/a/b/c.md", "../../1.md") 37 | -- "/1.md" == realpath("/a/b/c/d.md", "../../../1.md") 38 | function realpath(a, b) 39 | if a:sub(1,1) ~= '/' then a = '/' .. a end 40 | c = a:reverse() 41 | _,j = c:find("/") 42 | c = c:sub(j+1,-1) 43 | if b:sub(1,2) == "./" then 44 | b = b:sub(3) 45 | end 46 | while (true) do 47 | _,i = b:find("../") 48 | _,j = c:find("/") 49 | if i == nil or j == nil then break end 50 | b = b:sub(i,-1) 51 | c = c:sub(j+1,-1) 52 | end 53 | if b:sub(1,1) ~= '/' then b = '/' .. b end 54 | return c:reverse()..b 55 | end 56 | 57 | return {{ 58 | Blocks = get_description, 59 | Meta = function (meta) 60 | meta.description = description 61 | meta.runes = string.format("%0.1f", runes/1000) 62 | meta.read_time = string.format("%0.1f", runes/400) 63 | local envs = pandoc.system.environment() 64 | for k,v in pairs(envs) do 65 | if meta[k] == nil then 66 | meta[k] = v 67 | end 68 | end 69 | return meta 70 | end, 71 | Image = function (img) 72 | img.attributes.loading = "lazy" 73 | return img 74 | end, 75 | Link = function (link) 76 | local t = link.target 77 | if t:sub(1,4) ~= "http" and t:sub(-3) == ".md" then 78 | local envs = pandoc.system.environment() 79 | local url = envs["site_url"] 80 | local p = PANDOC_STATE.input_files[1] 81 | t = realpath(p, t) 82 | t = url .. t 83 | link.target = t:sub(1,-3).."html" 84 | end 85 | return link 86 | end, 87 | Para = function (para) 88 | local cs = para.content 89 | for k, v in ipairs(cs) do 90 | if v.t == 'SoftBreak' then 91 | local p = cs[k-1].text 92 | local n = cs[k+1].text 93 | 94 | if p ~= nil and n ~= nil then 95 | if (not is_ascii(p:sub(-1))) and (not is_ascii(n:sub(1,1))) then 96 | para.content[k] = pandoc.Str("") 97 | end 98 | end 99 | end 100 | end 101 | return para 102 | end, 103 | }} 104 | -------------------------------------------------------------------------------- /device.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | device_id=$1 4 | sites=/usr/local/etc/lehu/sites.txt 5 | syncs=/home/st/sync 6 | 7 | grep $device_id $sites | cut -d: -f1 | \ 8 | while read domain; do 9 | curl -s -H x-api-key:$api_key $host/rest/config/devices -d '{"deviceID":"'$device_id'"}' 10 | folder=/home/st/sync/$domain 11 | if [[ ! -d $folder ]]; then 12 | cp -R /home/st/sync/lehu.in $folder 13 | sed -E -i '/IMAP|ga_id|gt_id/d' $folder/env 14 | fi 15 | 16 | cat << EOF | curl -s -X PUT -H x-api-key:$api_key $host/rest/config/folders/$domain -d @- 17 | { 18 | "id": "$domain", 19 | "path": "$folder", 20 | "type": "sendreceive", 21 | "devices": [{ 22 | "deviceID": "$device_id" 23 | }], 24 | "fsWatcherEnabled": true, 25 | "fsWatcherDelayS": 1 26 | } 27 | EOF 28 | done 29 | -------------------------------------------------------------------------------- /feed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 生成 atom 订阅文件 4 | # 需要在 index.sh 生成 index.yaml 之后运行 5 | 6 | updated=`date -u +"%Y-%m-%dT%H:%M:%SZ"` 7 | 8 | # Feed 最多保留十条记录 9 | # 但头两行是 tiltle 和 articles 10 | # 所以从第三行开始 11 | head -n 12 $1/index.yaml > $1/feed.yaml 12 | 13 | pandoc -s -p -f markdown --wrap=none \ 14 | --metadata=updated:$updated \ 15 | --metadata-file=$1/feed.yaml \ 16 | --template $ROOT_DIR/feed.tpl \ 17 | --lua-filter $LUA_FILTER \ 18 | -o - /dev/null 19 | -------------------------------------------------------------------------------- /feed.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | $site_title$ 4 | $site_url$/ 5 | 6 | $author_name$ 7 | $author_email$ 8 | 9 | 10 | 11 | $updated$ 12 | $for(articles)$ 13 | 14 | $site_url$$it.path$ 15 | 16 | $it.title$ 17 | $it.updated$ 18 | $it.date$T00:00:00+08:00 19 | 20 | 21 | $endfor$ 22 | 23 | -------------------------------------------------------------------------------- /index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mds=$(find $1 -name '*.md' ! -name "draft-*.md") 4 | 5 | # 没有 markdown 则不生成 index.html 6 | if [[ -z "$mds" ]]; then 7 | exit 0 8 | fi 9 | 10 | # 支持跳过子目录 index.html 11 | if [[ ! -z "$no_child" ]]; then 12 | if [[ "$1" != "." ]]; then 13 | exit 0 14 | fi 15 | fi 16 | 17 | # index.html 没有对应的 markdown 文件件 18 | # 所以 title 变量为空,pandoc 会输出警告信息 19 | echo "title: no_warn" > $1/index.yaml 20 | echo "articles:" >> $1/index.yaml 21 | echo $mds | tr " " "\n" | xargs -I % meta.sh % | \ 22 | sort -r >> $1/index.yaml 23 | 24 | pandoc -s -p -f markdown --wrap=none \ 25 | --template index.tpl \ 26 | --metadata-file=$1/index.yaml \ 27 | --lua-filter $LUA_FILTER \ 28 | -o $1/index.html /dev/null 29 | -------------------------------------------------------------------------------- /map.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 生成 sitemap 文件 4 | 5 | source ./env 6 | 7 | cat << EOF 8 | 9 | 10 | EOF 11 | 12 | # - {"path":"/go/monkey.html","title":"Go语言实现猴子补丁","date":"2021-08-28"} 13 | grep '^- {"path":' $1/index.yaml | while read line; do 14 | path=$(echo $line|cut -d\" -f4) 15 | link="$site_url$path" 16 | updated=`date -u +"%Y-%m-%dT%H:%M:%SZ" -r $1$path` 17 | 18 | cat << EOF 19 | 20 | ${link} 21 | ${updated} 22 | 23 | EOF 24 | done 25 | 26 | echo "" 27 | -------------------------------------------------------------------------------- /meta.lua: -------------------------------------------------------------------------------- 1 | local description = "" 2 | 3 | function get_description(blocks) 4 | for _, block in ipairs(blocks) do 5 | if block.t == 'Para' then 6 | description = pandoc.utils.stringify(block) 7 | break 8 | end 9 | end 10 | return nil 11 | end 12 | 13 | return {{ 14 | Blocks = get_description, 15 | Meta = function (meta) 16 | meta.desc = description 17 | -- 触发脚本为 index.sh . 所以路径以 . 开头 18 | meta.path = PANDOC_STATE.input_files[1]:sub(2,-3).."html" 19 | return meta 20 | end, 21 | }} 22 | -------------------------------------------------------------------------------- /meta.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | updated=`date -u +"%Y-%m-%dT%H:%M:%SZ" -r $1` 4 | 5 | pandoc -s -p -f markdown --wrap=none \ 6 | --template $ROOT_DIR/meta.tpl \ 7 | --metadata=updated:$updated \ 8 | --lua-filter $ROOT_DIR/meta.lua \ 9 | -o - $1 10 | -------------------------------------------------------------------------------- /meta.tpl: -------------------------------------------------------------------------------- 1 | - { "date": "$date$", "path": "$path$", "title": "$title$", "desc": "$desc$", "updated": "$updated$" } 2 | -------------------------------------------------------------------------------- /systemd/lehu-build.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lehu build service 3 | After=network.target 4 | Requires=syncthing@st.service 5 | 6 | [Service] 7 | Environment=PATH=/home/st/lehu-sh:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 8 | EnvironmentFile=/usr/local/etc/lehu/env 9 | ExecStart=/home/st/lehu-sh/build-all.sh 10 | User=st 11 | Group=st 12 | Restart=on-failure 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /systemd/lehu-http.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lehu http service 3 | After=network.target 4 | Requires=lehu-http.socket lehu-https.socket lehu-quic.socket 5 | 6 | [Service] 7 | ExecStart=/usr/local/bin/ssltun -root /home/st/sync -users /usr/local/etc/lehu/users.txt -sites /usr/local/etc/lehu/sites.txt 8 | User=st 9 | Group=st 10 | Restart=on-failure 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /systemd/lehu-http.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lehu http socket 3 | 4 | [Socket] 5 | ListenStream=80 6 | BindIPv6Only=both 7 | FileDescriptorName=http 8 | Service=lehu.service 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | -------------------------------------------------------------------------------- /systemd/lehu-https.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lehu https socket 3 | 4 | [Socket] 5 | ListenStream=443 6 | BindIPv6Only=both 7 | FileDescriptorName=https 8 | Service=lehu.service 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | -------------------------------------------------------------------------------- /systemd/lehu-quic.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lehu quic socket 3 | 4 | [Socket] 5 | ListenDatagram=443 6 | BindIPv6Only=both 7 | FileDescriptorName=quic 8 | Service=lehu.service 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | --------------------------------------------------------------------------------