修改后的 Docker 版 Docsify,通过 Github 或 Gitea 的 Git Webhook 自动更新文档内容并重新生成侧边栏,新增了右侧标题栏插件,使用左边文件名 + 右边标题的布局

06-01 1349阅读

修改后的 Docker 版 Docsify

  • Docsify
    • 1 构建与运行
      • 1.1 构建 Docker 镜像
      • 1.2 运行 Docker 容器
      • 2 具体修改内容
        • 2.1 index.html 示例
        • 3 同步远程 Git 仓库实现远程修改
          • 3.1 同步 Git 仓库
          • 3.2 根据目录自动生成 \_sidebar.md 文件
          • 3.3 刷新 Docsify 页面
          • 4 根据远程 Git 仓库提交自动更新本地 Docsify 项目(以 Gitea 为例)
          • Obsidian
          • Gitea

            Docsify

            Docsify 是一个神奇的文档网站生成器。它能够快速地将 Markdown 文件转换为美观的文档网站,无需构建过程。它轻量、易用,支持自定义主题和插件扩展,非常适合用于项目文档、API 文档或知识库的构建。

            1 构建与运行

            本项目提供了一个基于 Docker 的 Docsify 环境,基于 stilleshan/docsify 镜像修改而成,新增了右侧标题栏插件,使用左边文件名+右边标题的布局,支持通过 Git Webhook 自动更新文档内容并重新生成侧边栏。

            • 教程:bilibili:修改后的Docker版Docsify,同步Github或Gitea自动更新文档内容并重新生成侧边栏,新增了右侧标题栏插件,使用左边文件名+右边标题的布局
            • Docker 镜像同步项目:https://github.com/sqing33/docker-image-sync

              CSDN:分享一下个人在使用 Docker 过程中无法下载镜像的一些解决方法

            • Docsify的github构建项目:https://github.com/sqing33/docker-docsify
            • Docsify的DockerHub:https://hub.docker.com/r/sqing33/docsify

              1.1 构建 Docker 镜像

              因为本地网络问题无法获取到源镜像stilleshan/docsify的元数据,所以将 dockerhub 同步到 github 的 ghcr 仓库以继续进行(ghcr.io/sqing33/docsify-stilleshan)

              docker buildx build \
                --platform linux/amd64,linux/arm64 \
                -t sqing33/docsify:latest \
                -t ghcr.io/sqing33/docsify:latest \
                --push .
              

              1.2 运行 Docker 容器

              你需要根据你的需求配置以下环境变量:

              • DOCSIFY_PORT: Docsify 服务监听的端口 (默认为 6158)。

              • ENABLE_GIT: 是否启用 Git 功能 (默认为 true)。

              • WEBHOOK_PORT: Webhook 监听器监听的端口 (默认为 6159)。

              • WEBHOOK_SECRET: 用于验证 Webhook 签名的密钥。

              • GIT_REMOTE_URL: 你的 Git 仓库的 URL (例如: https://gitea.example.com/user/repo.git)。

              • GIT_REMOTE: Git 远程仓库的名称 (默认为 origin)。

              • GIT_BRANCH: 你希望拉取和监听的分支 (默认为 main)。

                挂载容器内的 /docs 目录,以便于持久化存储文档内容。

                示例 docker run 命令:

                docker run -d \
                  --name docsify \
                  -p 6158:6158 \
                  -p 6159:6159 \
                  -e DOCSIFY_PORT=6158 \
                  -e ENABLE_GIT="true" \
                  -e WEBHOOK_PORT=6159 \
                  -e WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET \
                  -e GIT_REMOTE_URL=YOUR_GIT_REPO_URL \
                  -e GIT_REMOTE=origin \
                  -e GIT_BRANCH=main \
                  -e DOCS_DIR=/docs \
                  -v /vol1/1000/Docker/docsify/docs:/docs \
                  --restart unless-stopped \
                 sqing33/docsify:latest # 无法拉取可使用 ghcr.io/sqing33/docsify:latest
                

                示例 docker-compose.yml 文件:

                services:
                  docsify:
                    image: sqing33/docsify:latest # 无法拉取可使用 ghcr.io/sqing33/docsify:latest
                    container_name: docsify
                    ports:
                      # Docsify 服务器端口映射
                      - "6158:6158"
                      # Webhook 监听器端口映射,listener.js 在容器内部监听 6159 端口
                      - "6159:6159"
                    environment:
                      # Docsify 服务端口
                      - DOCSIFY_PORT=6158
                      # 是否启用 Git 更新
                      - ENABLE_GIT="true"
                      # WEBHOOK_PORT 监听端口
                      - WEBHOOK_PORT=6159
                      # Webhook 验证密钥
                      - WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
                      # Git 远程仓库的 URL,用于 entrypoint 中的 git remote add origin
                      - GIT_REMOTE_URL=YOUR_GIT_REPO_URL
                      # Git 远程源的名称,用于 listener.js 中的 git pull
                      - GIT_REMOTE=origin # 通常是 origin,如果你的仓库不是则修改
                      # Git 分支名称,用于 listener.js 中的 git pull 和事件判断
                      - GIT_BRANCH=main
                      - DOCS_DIR=/docs # 默认 /docs ,这应与卷挂载点匹配
                    volumes:
                      # 将宿主机目录挂载到容器内的 /docs
                      - '/vol1/1000/Docker/docsify/docs:/docs'
                    # 重启策略
                    restart: unless-stopped
                

                2 具体修改内容

                2.1 index.html 示例

                
                  
                    
                    Docsify
                    
                    
                    
                    
                    
                    
                    
                    
                      .sidebar {
                        padding: 20px 0 0;
                      }
                      .content {
                        padding-top: 0;
                      }
                    
                    
                  
                  
                    
                window.$docsify = { name: "Docsify", logo: "/_media/icon.svg", repo: "", loadSidebar: true, // 是否加载侧边栏 auto2top: true, // 是否自动滚动到页面顶部 toc: { tocMaxLevel: 6, target: "h1, h2, h3, h4, h5, h6", ignoreHeaders: [ "", "", ], }, count: { countable: true, fontsize: "0.9em", color: "rgb(90,90,90)", language: "chinese", }, copyCode: { buttonText: "复制", errorText: "复制错误", successText: "复制成功", }, }; function wrapLogoAndTitle() { return function (hook, vm) { hook.doneEach(function () { const appName = document.querySelector(".sidebar .app-name"); if (appName) { const logoImg = appName.querySelector("img"); const titleLink = appName.querySelector("a"); const titleText = vm.config.name; if (logoImg && titleLink) { // 创建包含logo和标题的新链接 const newLink = document.createElement("a"); newLink.href = titleLink.href; newLink.className = "app-name-link"; // 复制原始链接的事件监听器 newLink.onclick = titleLink.onclick; // 添加logo到新链接,并设置尺寸 const logoClone = logoImg.cloneNode(true); logoClone.style.width = "35px"; logoClone.style.height = "35px"; logoClone.style.objectFit = "contain"; // 保持图片比例 logoClone.style.verticalAlign = "middle"; // 添加标题到新链接 const titleSpan = document.createElement("span"); titleSpan.textContent = titleText; titleSpan.style.display = "inline-block"; titleSpan.style.verticalAlign = "middle"; titleSpan.style.marginLeft = "10px"; titleSpan.style.fontWeight = "bold"; titleSpan.style.fontSize = "1.2em"; titleSpan.style.lineHeight = "35px"; // 关键:使行高等于logo高度 newLink.appendChild(logoClone); newLink.appendChild(titleSpan); // 替换原始链接 appName.innerHTML = ""; appName.appendChild(newLink); document.head.appendChild(style); } } }); }; } // 注册插件时可以传入自定义配置 window.$docsify.plugins = [].concat( wrapLogoAndTitle(), window.$docsify.plugins || [] );

                插件包含:右侧标题栏、字数统计、emoji 解析、图片缩放、代码复制、自定义左上角 logo + 标题

                修改后的 Docker 版 Docsify,通过 Github 或 Gitea 的 Git Webhook 自动更新文档内容并重新生成侧边栏,新增了右侧标题栏插件,使用左边文件名 + 右边标题的布局

                3 同步远程 Git 仓库实现远程修改

                3.1 同步 Git 仓库

                • 在映射的数据目录/vol1/1000/Docker/docsify/docs:/docs’新建 git 仓库:
                  git init
                  git remote add origin http://192.168.1.100:3111/sqing/wiki.js.git
                  git config --global --add safe.directory /docs
                  
                  • 添加.gitignore文件设置忽略项
                    index.html
                    generate-sidebar.js
                    _sidebar.md
                    _media
                    
                    • 同步远程 Git 仓库到本地
                      git pull origin main
                      

                      3.2 根据目录自动生成 _sidebar.md 文件

                      // dockerfile/generate-sidebar.js
                      const fs = require('fs');
                      const path = require('path');
                      // 脚本假设在 /docs 目录下运行
                      const docsDir = '.'; // 你的文档根目录,通常是当前目录
                      const sidebarFile = '_sidebar.md';
                      let sidebarContent = '';
                      // 排除列表
                      const excludeList = [
                          '_sidebar.md',       // 排除侧边栏文件本身
                          '_media',            // 排除媒体文件
                          'index.html',        // 排除主页 HTML 文件
                          '.git',              // 排除 .git 目录
                          '.gitignore',        // 排除 .gitignore 目录
                          'readme.md',         // 排除 readme.md 文件
                          'git_listen',      // 排除 git_listen 文件夹
                      ];
                      function buildSidebar(currentDir, level) {
                        const items = fs.readdirSync(currentDir);
                        // 对items进行排序,让侧边栏顺序更可控,例如按字母排序
                        // 确保排序时不区分大小写,并处理中文等非英文字符(如果需要更复杂的排序)
                        items.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
                        items.forEach(item => {
                          // 在处理前检查是否在排除列表中
                          // 使用 path.basename 确保只比较文件名/目录名本身
                          if (excludeList.includes(item) || item.startsWith('.')) {
                              console.log(`Excluding: ${item}`); // 可选:打印排除项
                              return; // 跳过当前循环迭代
                          }
                          const itemPath = path.join(currentDir, item);
                          const stat = fs.statSync(itemPath);
                          // 使用两个空格作为基本缩进,因为 docsify 对缩进要求可能不同
                          const indent = '  '.repeat(level);
                          if (stat.isDirectory()) {
                            // 在目录名后添加斜杠以便清晰地表示目录
                            sidebarContent += `${indent}* **${item}**\n`; // 可以加粗目录名
                            // 递归处理子目录,层级加1
                            buildSidebar(itemPath, level + 1);
                          } else if (stat.isFile() && item.endsWith('.md')) {
                            const fileNameWithoutExt = path.parse(item).name;
                            // 排除 README.md 文件本身,如果需要链接它,应该在父级目录处特殊处理
                             if (fileNameWithoutExt.toLowerCase() === 'readme') {
                                 console.log(`Excluding README.md file entry: ${item}`);
                                 return; // 跳过 README.md 的文件条目
                             }
                            const linkPath = path.relative(docsDir, itemPath).replace(/\\/g, '/'); // 确保路径使用正斜杠
                            sidebarContent += `${indent}* [${fileNameWithoutExt}](${linkPath})\n`; // 使用原始文件名作为链接文本
                          }
                        });
                      }
                      // 从根目录(/docs,因为脚本会在那里运行)开始构建
                      buildSidebar(docsDir, 0);
                      // 在顶部添加一个指向根目录(README.md 或 index.html)的链接
                      // 这通常是 Docsify 侧边栏的第一项
                      sidebarContent = `* [首页](/)\n` + sidebarContent;
                      // 写入 _sidebar.md 文件
                      fs.writeFileSync(sidebarFile, sidebarContent);
                      console.log(`${sidebarFile} generated successfully.`);
                      

                      3.3 刷新 Docsify 页面

                      4 根据远程 Git 仓库提交自动更新本地 Docsify 项目(以 Gitea 为例)

                      1. 在挂载的项目路径下新建一个git_listen目录,在其中新建一个listener.js文件,内容如下:
                      // dockerfile/listener.js (只包含监听和命令执行逻辑)
                      const express = require('express');
                      const bodyParser = require('body-parser');
                      const crypto = require('crypto');
                      const { exec } = require('child_process');
                      const app = express();
                      // --- 配置 ---
                      // 从环境变量读取配置,如果未设置则使用默认值
                      const PORT = process.env.WEBHOOK_PORT || 6159;
                      // !! IMPORTANT !!: 从环境变量读取密钥
                      const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
                      // 当前容器内的文档目录,从环境变量读取或使用默认值
                      const DOCS_DIR_IN_CONTAINER = process.env.DOCS_DIR || '/docs';
                      // 从环境变量读取 Git 配置
                      const GIT_REMOTE = process.env.GIT_REMOTE || 'origin'; // 用于 git pull 命令
                      const GIT_BRANCH = process.env.GIT_BRANCH || 'main'; // 用于 git pull 和事件类型判断
                      // 检查是否设置了必需的环境变量
                      if (!WEBHOOK_SECRET) {
                          console.error("错误: 必须设置 WEBHOOK_SECRET 环境变量。");
                          // 在实际生产环境中,可能需要更优雅地处理或延迟启动
                          // 为了脚本能立即运行,这里只警告,但 webhook 将无法验证
                          // process.exit(1); // 如果密钥未设置就退出,但这样容器会停止
                      }
                      // GIT_REMOTE 和 GIT_BRANCH 如果未设置有默认值,GIT_REMOTE_URL 用于 entrypoint 中的 git remote add
                      // --- 中间件 ---
                      // 使用原始 body 以便进行签名验证
                      app.use(bodyParser.raw({ type: 'application/json' }));
                      // --- Webhook 处理路由 ---
                      app.post('/webhook', (req, res) => {
                          console.log('接收到 Webhook 请求。');
                          // 1. 验证签名 (Gitea 使用 x-gitea-signature)
                          const signature = req.headers['x-gitea-signature'];
                          if (!signature) {
                              console.warn('接收到没有 x-gitea-signature 签名的 Webhook 请求。');
                              return res.status(401).send('Signature required');
                          }
                          const payload = req.body;
                          const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
                          hmac.update(payload);
                          const digest = hmac.digest('hex');
                          if (digest !== signature) {
                              console.warn('无效的 Webhook 签名。');
                              return res.status(401).send('Invalid signature');
                          }
                          console.log('Webhook 签名验证成功。');
                          let payloadJson;
                          try {
                              payloadJson = JSON.parse(payload.toString());
                          } catch (e) {
                              console.error('解析 Webhook Payload JSON 失败:', e);
                              return res.status(400).send('Invalid JSON payload');
                          }
                          // 2. 判断事件类型 (Gitea 使用 x-gitea-event)
                          const eventType = req.headers['x-gitea-event'];
                          console.log(`Webhook 事件类型: ${eventType}`);
                          // 只处理 push 事件
                          if (eventType === 'push') {
                              // 判断是否是指定的分支
                              // ref 的格式是 'refs/heads/分支名' 或 'refs/tags/标签名'
                              const pushBranch = payloadJson.ref.split('/').pop(); // 从 'refs/heads/main' 中提取 'main'
                              if (pushBranch !== GIT_BRANCH) {
                                  console.log(`Push 事件发生在分支 ${pushBranch},但只监听分支 ${GIT_BRANCH}。忽略。`);
                                  return res.status(200).send(`Ignored event for branch '${pushBranch}'`);
                              }
                              console.log(`接收到分支 ${GIT_BRANCH} 的 Push 事件。`);
                              // 3. 执行容器内部的命令:进入 docs 目录,拉取最新更改,然后运行 generate-sidebar.js
                              console.log(`正在容器内部执行更新命令: cd ${DOCS_DIR_IN_CONTAINER} && git pull ${GIT_REMOTE} ${GIT_BRANCH} && node generate-sidebar.js`);
                              // 构建在容器内部执行的命令序列
                              // 注意:如果 generate-sidebar.js 不在 DOCS_DIR_IN_CONTAINER 根目录,需要调整路径
                              const containerCommands = `cd ${DOCS_DIR_IN_CONTAINER} && git pull ${GIT_REMOTE} ${GIT_BRANCH} && node generate-sidebar.js`;
                              exec(containerCommands, (error, stdout, stderr) => {
                                  if (error) {
                                      console.error(`执行命令失败: ${error.message}`);
                                      // 即使失败,也回复 500 以外的状态码,避免 Gitea 重试过多,但通常 500 是可以的
                                      return res.status(500).send('Failed to update docs');
                                  }
                                  if (stderr) {
                                      console.error(`命令的标准错误输出: ${stderr}`);
                                  }
                                  console.log(`命令的标准输出: ${stdout}`);
                                  const now = new Date();
                                  const timestamp = now.toLocaleString();
                                  console.log(`文档更新并侧边栏生成成功。完成时间: ${timestamp}`);
                                  res.status(200).send('Docs updated');
                              });
                          } else {
                              // 忽略其他类型的事件
                              console.log(`事件类型 '${eventType}' 已忽略。`);
                              res.status(200).send(`事件类型 '${eventType}' 已忽略`);
                          }
                      });
                      // --- 启动服务 ---
                      app.listen(PORT, () => {
                          console.log(`Webhook 监听服务正在容器内部运行,端口号: ${PORT}`);
                          console.log(`监听分支: ${GIT_BRANCH} 的 push 事件`);
                          console.log(`Git 远程源名称: ${GIT_REMOTE}`); // 注意这里是远程源名称,不是 URL
                          console.log(`文档目录: ${DOCS_DIR_IN_CONTAINER}`);
                          // 注意:WEBHOOK_SECRET 不应该打印到日志,因为它是一个密钥
                      });
                      
                      1. 设置listener.js的端口PORT、为 Gitea Webhook 设置的 Secret TokenWEBHOOK_SECRET,GIT_REMOTE和GIT_BRANCH根据创建的 git 仓库填写

                      2. 进入 Gitea 仓库的设置 -> 点击Web 钩子(Webhooks) -> 添加 Web 钩子 -> 选择 Gitea

                      3. 目标 URL设置为http://192.168.1.100:6159/webhook,HTTP 方法为POST,密钥文本填WEBHOOK_SECRET,触发条件:选择所有事件,点击添加 Web 钩子

                      4. 进入 Docsify 容器的终端 -> 进入目录/docs/Gitea listen -> 安装依赖npm install express body-parser crypto -> 运行监听服务node listener.js

                      Obsidian

                      Obsidian 是一款强大的笔记应用,它使用 Markdown 格式存储笔记,并支持双向链接,使其成为构建个人知识库的理想工具。 它可以通过插件扩展功能,并支持多种导出格式。

                      把 Obsidian 粘贴的图片上传到 Docker 部署的 PicList 上的具体过程与一些目前使用的插件推荐

                      Gitea

                      Gitea 是一个自托管的 Git 服务,类似于 GitHub 或 GitLab,但更轻量级,易于安装和管理。 它允许用户在自己的服务器上托管 Git 仓库,进行代码版本控制、协作和代码审查。

                      services: 
                        server  :
                          image: ghcr.nju.edu.cn/sqing33/gitea # 源镜像:gitea/gitea 
                          container_name: gitea 
                          restart: always 
                          volumes  :
                            - /vol1/1000/Docker/gitea/data:/data 
                            - /etc/timezone:/etc/timezone:ro 
                            - /etc/localtime:/etc/localtime:ro 
                          ports  :
                            - "3111:3000" 
                            - "2222:2222" 
                      
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码