跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

快速上手

从官方 OINK Starter 建立可运行的本地基线,再依次定制内容、语言、品牌、集成与部署。

新站点的推荐起点是 pgsty/oink-starter,而不是复制本站这个 文档与回归测试仓库。Starter 是公开的 GitHub 模板:它固定 OINK v1.0.0,默认即可构建,只包含中性的项目示例与部署 workflow。

两个版本号承担不同职责

OINK 声明的兼容性下限是 Hugo Extended 0.160.1。当前 Starter 与它的 CI 固定使用 Hugo Extended 0.165.0 和 Go 1.27。下面这条路径应 使用 Starter 固定的工具链;只有刻意维护旧环境的既有站点才使用较低的兼容下限。

选择起点

当前情况 推荐路径 得到什么
新建文档站或项目站 OINK Starter 一套精简的三语 Docs、Blog、Book 站点与两条部署 workflow
已有 Hugo 站点 从零安装 不替换内容,只补 OINK 模块与 Goldmark 前置配置
已有 Docsy 或旧版 OINK 站点 版本升级 保留内容,迁移受支持的语法,并审查站点覆盖

五分钟建立基线

  1. 安装工具

    安装 Git、Go 1.27 或更新版本,以及 Hugo Extended 0.165.0 或更新版本。Hugo 输出必须包含 extended

    $ go version
    go version go1.27.0 darwin/arm64
    $ hugo version
    hugo v0.165.0+extended+withdeploy darwin/arm64
    

    macOS 可以执行 brew install git go hugo。Linux 与 Windows 请按官方 Hugo 安装指南Go 下载页安装,并确认选择 Hugo Extended

  2. 创建或克隆站点

    准备长期维护时,请打开 Starter 仓库并点击 Use this template,然后克隆 GitHub 为你创建的新仓库。只想在本机评估原始模板时执行:

    git clone https://github.com/pgsty/oink-starter.git my-docs
    cd my-docs
    hugo server
  3. 打开基线

    打开 http://localhost:1313/。默认 Starter 还在 /zh/ 发布中文,在 /fr/ 发布法语。开始修改前,先确认 Docs、Blog、Book、本地搜索、语言切换与深浅色 模式都能工作。

  4. 完成一个可见修改

    修改 hugo.yaml 顶部的站名与规范 URL,再修改 data/home/en.yaml 中的一句话。 浏览器刷新后能同时看到两处变化,才算证明配置、内容与固定版本的主题已经正确连通。

由浅入深地定制

  • 使用 OINK Starter — 先改身份,再依次处理语言、首页、 内容、导航、品牌、集成与部署。
  • Starter 仓库导览 — 每个文件负责什么,哪些要替换, 哪些可以删除。
  • 编写页面 — front matter、标题、链接、图片、草稿与页尾控件。
  • 组件总览 — 内容树稳定后,再增加表达能力。
  • 品牌外观 — Logo、强调色、字体、页宽与 CSS 扩展点。
  • 发布上线 — 使用内置 GitHub Pages 或 Cloudflare Pages workflow,再验证真实公开路由。

这个顺序是有意的。先证明构建与内容树,再逐项增加定制,比同时修改语言、导航、 CSS、分析与托管更容易定位问题。

发布门禁

第一次推送前,执行与 Starter workflow 相同的严格生产构建:

hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning

命令以 Total in … 结束、没有警告或错误,而且 public/ 中存在各语言根与代表性的 Docs、Blog、Book 路由,才算通过。此时仍只证明本地构建:本地构建、提交、推送、 workflow 变绿与公开站点正确,是彼此独立的关卡。

下一步

继续阅读完整 Starter 教程。如果模板有你不需要的结构, 按仓库导览安全删减。只有在给既有站点接入 OINK,或者 明确想亲手组装每个文件时,才走从零建站路径。

1 - 使用 OINK Starter

按身份、语言、首页、内容、导航、品牌、集成、部署的顺序,把官方 Starter 逐层变成你的项目站点。

pgsty/oink-starter 是新建 OINK 站点的正式起点。它刻意小于 oink.pgsty.com:不会把主题文档、分析账号、评论仓库、 浏览器回归套件或 PGSTY 品牌复制进你的项目。

当前模板固定 OINK v1.0.0、Go 1.27 与 Hugo Extended 0.165.0。 默认三语、仅英文、英中双语三个 profile 都已经在这个版本上完成 warning 即失败的 严格构建。

模板包含什么

表面 内置基线 第一个决定
语言 英语、简体中文、法语 保留三语,或选择内置单语 / 双语 profile
内容 Docs、Blog 与一本简短 Book 教程 重写示例;确认整个表面不需要时才整棵删除
首页 每种语言一份精简 data/home/<lang>.yaml 替换项目承诺与入口
品牌 中性 Logo 与 favicon 有正式项目图形之前先保留
集成 仓库、Giscus、分析、分享、反馈示例均被注释 只启用你准备长期运营的完整配置
部署 GitHub Pages 与 Cloudflare Pages Direct Upload workflow 选择一条生产路径并验证真实 URL

Starter 自己的 /book/ 是一份从预览到部署的四章短教程。本页是维护者级版本: 说明修改顺序、各层边界,以及每层之后应执行的检查。

创建自己的仓库

推荐使用 GitHub 模板

打开 Starter 仓库,点击 Use this template → Create a new repository,再克隆 GitHub 在你的账号或组织下 创建的仓库:

git clone https://github.com/OWNER/PROJECT-DOCS.git
cd PROJECT-DOCS
hugo server

这样站点从一开始就有自己的 Git 历史,原始 Starter 只是上游参考,不会成为一个 可能误推送的 remote。

克隆原始仓库进行评估

只做一次性本地评估时执行:

git clone https://github.com/pgsty/oink-starter.git
cd oink-starter
hugo server

真实项目不要从删除这个 clone 的 .git 目录开始。GitHub 模板操作已经创建了清晰的 项目边界,并保留可审计的初始提交。

修改前先预览

依次打开:

  • //zh//fr/:三个首页;
  • /docs//blog//book/:三种内容表面;
  • 任意一组译文,再操作语言切换器;
  • 本地搜索、深浅色切换,以及一个窄屏视口。

同时记录实际解析的模块:

hugo mod graph | grep github.com/pgsty/oink

结果应当是 github.com/pgsty/oink@v1.0.0。这份未修改的预览,是后面 判断每次改动的基线。

分层定制

第一层:站点身份

修改 hugo.yaml 顶部标有 CHANGE ME 的两个值:

hugo.yaml
title: &siteTitle Project Name
baseURL: https://example.org/

标题的 YAML 锚点会把站名带进所有已启用语言。接着修改版权人,并在新仓库已存在后 取消仓库链接的注释:

hugo.yaml
params:
  copyright:
    authors: '[项目贡献者](https://example.org/community/)'
    from_year: 2026
  github_repo: https://github.com/OWNER/PROJECT-DOCS
  github_branch: main

重新运行 hugo server,检查浏览器标题、页脚、编辑 / 历史链接与 canonical URL。 项目图形尚未定稿时先不要改 Logo;文字身份更容易先完成评审。

第二层:语言 profile

根配置默认启用英语、中文和法语。如果这不是目标语言组合,请在其它配置修改之前 选择内置 profile:

cp examples/hugo.single.yaml hugo.yaml     # 仅英文
cp examples/hugo.bilingual.yaml hugo.yaml  # 英文 + 中文

这两份是完整的最小配置,不是可以叠加的片段;复制会覆盖根文件里那些被注释的集成 示例。因此应在最开始做;hugo.yaml 已有项目修改时,只合并 languagesdisableLanguages,不要整文件覆盖。

未启用语言仍保留声明,让 Hugo 能识别 .zh.md.fr.md 是译文并安全忽略。 要永久移除一种语言,先确认所选 profile 能构建,再删除对应内容与首页数据。

第三层:首页

首页是数据,不是难以维护的整页模板覆盖:

data/home/en.yaml
data/home/zh.yaml
data/home/fr.yaml

先改一种语言。每个文件里的 sections 决定顺序,herocardscta 提供内容。 保持结构,替换项目承诺、目标 URL 与示例卡片。第一种语言确认无误后,再把同一组事实 翻译到已启用语言。

需要其它组合时,使用首页与落地页中的完整注册表;不要复制 Starter 的首页 partial,因为这里本来就没有站点自有模板。

第四层:内容与导航

重写或删除 content/ 下的示例叶子页面。确定整个表面不属于你的项目之前,先保留 栏目根:

content/docs/  参考与任务文档
content/blog/  文章、设计记录与发布说明
content/book/  连续阅读的长篇指南

内容树就是侧栏。顶部导航写在各语言 _index 根页的 menus.main 里,因此给 Docs、 Blog 或 Book 改名时,修改发生在它所描述的内容旁边,而不是另一棵全局菜单树。译文 并排放置,对应标题使用相同的显式 ID:

page.md
page.zh.md
page.fr.md

新增自定义导航数据之前,先读组织内容;大多数站点使用生成树 已经足够。

第五层:品牌与阅读功能

正式图形准备好后,替换 assets/icons/logo.svgstatic/favicon.svg。随后一次只启用 一组最小而有用的配置:

hugo.yaml
params:
  ui:
    theme_color: '#245f94'
    typography: system
    image_zoom: true
    share: [mastodon, linkedin, email, copy]

自定义本地字体时,用 params.ui.fonts 写字体族,或者在站点 CSS 中声明字体文件。 布局、侧栏、搜索与组件配置应查询配置总览,不要复制 oink.pgsty.com 那份大得多的站点配置。

第六层:外部集成

Starter 默认关闭或注释了仓库操作、Giscus、Google Analytics、反馈与分享。只有 必需事实全部明确时才启用:

  • 仓库链接需要真实 owner、repository 与 branch;
  • Giscus 需要仓库 / 分类名称和不可变 ID;
  • Google Analytics 需要项目自己的 measurement ID;
  • 反馈只有在分析存在时才记录结构化 gtag 事件;
  • 助手链接会把当前 URL 发送给第三方,因此必须做显式策略选择。

不完整的可选块应继续保持注释。各集成的运营边界见启用评论分析与 SEO仓库与页面信息

构建与部署

严格本地构建

启用托管 workflow 前执行:

hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning

提交 hugo.yamlgo.modgo.sum;不要提交生成的 public/resources/、模块 缓存或本地模块替换。

GitHub Pages

Starter 已包含 .github/workflows/github-pages.yaml。在 Settings → Pages 中选择 GitHub Actions 作为 Source。推送到 main 后, workflow 使用固定工具链构建,向 GitHub 查询正确的项目子路径,再通过 Pages 部署 API 发布 public/

Cloudflare Pages

内置 .github/workflows/cloudflare-pages.yaml 使用 Direct Upload。创建 Pages Direct Upload 项目,添加 CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_API_TOKEN,再手动 运行一次 workflow。设置仓库变量 CLOUDFLARE_PAGES_ENABLED=true 后才会自动部署; 规范地址不是默认 pages.dev 域名时,再设置 CLOUDFLARE_SITE_URL

同一个项目只选 Direct Upload 或 Cloudflare Git integration 其中一种。完整托管对比 与 baseURL 规则见发布上线

验证并删除示例

宣布站点完成前:

  1. 搜索 Project Nameexample.orgOWNERPROJECT 等占位符,逐项确认剩余位置 是否有意保留。
  2. 在桌面与移动端打开每种已启用语言的根,以及代表性的 Docs、Blog、Book 页面。
  3. 确认语言切换落到对页,而不是首页。
  4. 验证搜索、深色模式、一个组件、Markdown 输出、打印、404、canonical URL 与仓库操作。
  5. 把部署 workflow 和公开 URL 与本地构建分开检查。

删除示例 Book 或 Blog 之前,要同时移除对应顶部菜单根,以及首页上指向它的卡片。每整棵 删除一个表面就严格重建一次,才能让失败归因到单一改动。

下一步

Starter 仓库导览查询文件职责,再继续阅读 编写页面配置总览。已有站点不应 继承 Starter 内容模型时,改走从零建站路径。

2 - Starter 仓库导览

oink-starter 的文件级地图:身份、语言、首页、内容、导航、品牌、部署与固定主题分别由哪里管理。

本页说明从 pgsty/oink-starter 创建的仓库,不再介绍大得多的 oink.pgsty.com 文档与回归测试仓库。主题源码不会 复制进任何一个站点:go.mod 以 Hugo Module 形式固定版本,Hugo 把解析结果存进 Go 模块缓存。

顶层地图

oink-starter/

  • oink-starter/
    • hugo.yaml身份、语言、输出、参数与模块导入
    • go.mod站点模块与精确 OINK 版本
    • go.sum模块校验和
    • examples/
      • hugo.single.yaml仅英文的完整 profile
      • hugo.bilingual.yaml英文 + 中文的完整 profile
    • data/
      • home/
        • en.yaml每种语言一份精简落地页
        • zh.yaml
        • fr.yaml
    • content/
      • _index.md各语言首页根
      • _index.zh.md
      • _index.fr.md
      • docs/简介、快速上手、教程、参考
      • blog/文章、设计记录、发布说明
      • book/介绍 Starter 的连续教程
    • assets/
      • icons/logo.svg经 Hugo 处理的项目 Logo
    • static/
      • favicon.svg原样复制到站点根
    • i18n/
      • fr.yamlStarter 自有法语界面覆盖
    • .github/workflows/
      • github-pages.yaml严格构建与 GitHub Pages 部署
      • cloudflare-pages.yaml严格构建与 Cloudflare Direct Upload
    • README.md面向仓库维护者的操作摘要
    • LICENSE模板源码许可证

生成的 public/resources/.hugo_build.lock 与模块缓存是被忽略的构建状态, 不是源码。

最先修改什么

路径 职责 第一次操作
hugo.yaml 身份、规范 URL、语言、输出、主题功能、可选集成 修改两个标记值;其它修改前先选择语言 profile
data/home/ 首页承诺、卡片与行动入口 一种语言确认后,再重写所有已启用语言
content/ 全部读者可见内容 替换示例叶子;确认整个表面不要时才删除栏目根
assets/icons/logo.svg 经处理的 Logo 有正式图形后再替换
static/favicon.svg 浏览器图标 与 Logo 一起评审后替换
hugo.yaml 中的 params.github_* 编辑、历史、新建页面与 issue 链接 目标仓库已存在后才取消注释

哪些必须保留

  • go.modgo.sum:两者共同固定并校验 OINK v1.0.0,都要提交。
  • hugo.yaml 中三项 Goldmark 设置:原生 Steps、Cards、Fields、图片属性与 Book 目标都依赖它们。
  • outputs:删除 markdownLLMSprint,会有意删除对应的 Markdown、 Agent 索引或打印表面。
  • workflow 中的 fetch-depth: 0:保留 enableGitInfo 时,最后修改与贡献者事实需要 完整 Git 历史。
  • CI 中的 GOWORK: offHUGO_MODULE_WORKSPACE: off:开发者本地 workspace 不得 替换 CI 正在验证的公开版本。

可选表面

Docs、Blog 与 Book 是彼此独立的顶层表面。安全删除其中一个的顺序是:

  1. 删除对应的 content/<surface>/ 内容树;
  2. 删除首页指向它的卡片或链接;
  3. 确认其它页面不再链接它;
  4. 严格构建,并检查剩余顶部导航。

不要只删除某种语言的栏目根:那会形成难以区分「有意不对称」与「漏译」的语言专属导航 和回退行为。要么在所有已启用语言中删除整个表面,要么明确记录这种不对称。

完成语言选择后,examples/ 下两个配置 profile 可以删除,也可以作为参考保留;真正 生效的站点配置只有根目录 hugo.yaml

内容与导航

Docs 与 Book 下的目录结构和 weight 共同形成侧栏与翻页顺序。顶部导航来自栏目根的 menus.main。译文根重复相同的 identifierparent 与 weight,只翻译可见标签。

Starter 刻意演示 Documentation System 内容模型:

  • 简介回答是什么、为什么;
  • 快速上手帮助新用户得到结果;
  • 教程带领读者完成端到端任务;
  • 参考记录精确的受支持行为。

可以按项目需要改名或重组,但应保留不同学习路径之间的分工,不要把所有答案混进一棵树。

语言模型

英文源码以 .md 结尾,中文和法语对页分别以 .zh.md.fr.md 结尾。首页数据按 data/home/ 下的语言键分文件。根 profile 声明语言、locale、顺序与站点描述。

单语与双语 profile 仍声明被禁用的语言,这是有意设计:Hugo 会把未使用后缀识别为 译文,而不会把多个文件渲染到同一个英文 URL。只在项目配置开始前复制 profile;之后 应手工合并。

OINK 在哪里

两个文件建立模块边界:

hugo.yaml
module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: '0.160.1'
go.mod
module github.com/OWNER/PROJECT-DOCS

go 1.27.0

require github.com/pgsty/oink v1.0.0

hugo mod graph 显示实际解析版本。生产使用 go.mod 中的精确标签;本地 HUGO_MODULE_REPLACEMENTS 只是开发覆盖,绝不能提交,也不能当成发布证明。

部署文件

GitHub Pages workflow 在推送 main 后自动运行;仓库设置必须选择 GitHub Actions 作为 Pages Source。Cloudflare workflow 默认手动运行,只有仓库变量 CLOUDFLARE_PAGES_ENABLED=true 存在时才自动执行;所需账号 ID 与 API token 始终 保存在仓库 secrets 中。

只保留实际运营的部署路径。Cloudflare Direct Upload 与 Cloudflare Git integration 是同一个项目的两种所有权模型,不是应当同时运行的两道关卡。

安全的定制顺序

  1. 证明未修改的预览可用。
  2. 修改身份并选择语言。
  3. 替换一种首页,再补齐译文。
  4. 替换内容并验证导航。
  5. 品牌与阅读功能一次只改一组。
  6. 启用完整的外部集成。
  7. 执行严格生产构建。
  8. 部署,再独立验证生产环境。

仓库已经属于自己后,每层之间做一次提交。小边界能让后续回归与回滚明确归因到一个决定。

验证

hugo mod graph | grep github.com/pgsty/oink
hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning
git status --short

模块图应显示固定发布,构建没有警告或错误,Git 状态只包含源码修改而没有 public/ 或 缓存。之后打开所有已启用语言的根,以及代表性的 Docs、Blog、Book 路由,再进入部署。

3 - 从零建站与其它安装方式

从空目录搭一个最小 OINK 站点,以及 Module / submodule / 离线归档 / 克隆四种安装方式的取舍。

这是推荐路径 OINK Starter 的手工替代方案。本页从空目录 搭建一个最小 OINK 站点:一份精简 hugo.yml 加一条 hugo mod get,得到一个可预览 的单语站点。代价是首页、示例内容、部署 workflow 与每种组件用法都要自己组装。

已有 Hugo 站点时不需要脚手架:装上主题模块,再补三项 goldmark 前置配置(见hugo.yml),正文不用重写。已有 Docsy 站点见版本升级

后半部分是四种安装方式的取舍:Hugo Module、Git submodule、离线归档、固定版本克隆。 当前 v1.0.0 发布路径应使用 Go 1.27 与 Hugo Extended 0.165.0;只有刻意维护旧环境的 既有站点才使用主题声明的较低兼容下限。

从空目录到第一页

  1. 建骨架并获取主题

    hugo new site --format yaml my-docs
    cd my-docs
    hugo mod init github.com/example/my-docs
    hugo mod get github.com/pgsty/oink@v1.0.0

    hugo mod init 后面跟的是你自己站点的模块路径,通常就是仓库地址。hugo mod get 会写出 go.modgo.sum,两个都要提交。

    最新版本号在 GitHub Releases;本页出现的 v1.0.0 是本站当前固定的版本。生产站点固定到发布标签,不要跟随 main@latest 是一次性解析动作,不是版本策略。

  2. hugo.yml

    hugo new site 生成的 hugo.yaml 改名为 hugo.yml(两个后缀 Hugo 都接受,本文统一用后者),内容替换为下面这份,可直接构建:

    hugo.yml
    title: Product Docs
    baseURL: https://docs.example.com/
    defaultContentLanguage: en
    # enableGitInfo: true        # 页面「最后修改」时间来自 git,先 git init 再打开
    
    languages:
      en:
        label: English
        locale: en-US
        weight: 1
        title: Product Docs
        params:
          description: Everything about running Product in production
        menus:
          main:
            - { name: Docs, pageRef: /docs, weight: 20 }
            - { name: Blog, pageRef: /blog, weight: 50 }
    
    # 三项 Goldmark 前置:OINK 的原生 Markdown 组件全靠它们
    markup:
      goldmark:
        renderer:
          unsafe: true # 允许内容里的行内 HTML
        parser:
          attribute:
            block: true # {.steps} {.cards} {caption=} 这类属性行
          wrapStandAloneImageWithinParagraph: false # 块级图片才能带属性行
      highlight:
        noClasses: false # 代码配色跟随深浅色模式
    
    params:
      offline_search: true
      github_repo: https://github.com/example/product-docs
      copyright:
        authors: '[Example Inc.](https://example.com/)'
        from_year: 2026
      ui:
        dark_mode: true
        sidebar_menu_foldable: true
        section_index: cards
    
    outputs:
      home: [HTML, markdown, LLMS]
      page: [HTML, markdown]
      section: [HTML, RSS, print, markdown]
    
    module:
      imports:
        - path: github.com/pgsty/oink
      hugoVersion:
        extended: true
        min: '0.160.1'

    五段分别管什么:

    管什么 少了会怎样
    顶层 + languages 站名、域名、语言与顶栏菜单 baseURL 不对,线上所有绝对链接指错
    markup.goldmark 三项组件前置 属性行变成正文里的一行 {.steps}
    params 搜索、仓库链接、外壳开关 交互功能默认关闭,主题不替站点决定
    outputs 每页的 .mdllms.txt、打印页 页面菜单里没有「复制 Markdown」,也没有打印视图
    module 引用主题、声明 Hugo 下限 构建时找不到主题

    写公式还需要 Goldmark 的 passthrough 扩展,见公式。每个键的完整含义与默认值见配置总览

  3. 写第一页

    content/ 下的每个一级目录是一个分区,目录结构就是侧栏结构。文档分区至少要有一个 _index.md

    content/docs/_index.md
    ---
    title: Docs
    linkTitle: Docs
    description: Everything about running Product in production.
    weight: 20
    ---
    
    从[安装](/docs/install/)开始。
    content/docs/install.md
    ---
    title: Install
    description: Install Product on a fresh machine.
    weight: 10
    ---
    
    ## Prerequisites {#prerequisites}
    
    > [!IMPORTANT]
    > Product 需要 PostgreSQL 18 或更高版本。
    
    ## Install {#install}
    
    ```bash
    curl -fsSL https://get.example.com | bash
    ```

    标题写显式 {#id}:后续加译文时两种语言的锚点才能对应。页面写法见编写页面

  4. 预览

    hugo server

    打开 http://localhost:1313/,侧栏里有 Docs → Install。修改文件是毫秒级热重载。

其它安装方式

上面用的是 Hugo Module。另外三种方式面向特定约束:网络隔离、平台要求构建输入包含完整主题树、组织内部需要评审主题副本。除 hugo mod vendor 之外,它们都不建立 Go 模块,站点用 theme: oink 而不是 module.imports 引用主题;共同的代价是版本解析与完整性校验由你自己负责。

Hugo Module(推荐)

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@v1.0.0
hugo.yml
module:
  imports:
    - path: github.com/pgsty/oink

唯一能让 Hugo 自己解析版本、校验 checksum、并在 go.sum 里留下审计记录的方式。hugo mod graph 看实际解析结果,hugo mod get -u 升级。需要本机有 Go。

Git submodule

在站点仓库里记录准确的主题 commit:

git submodule add https://github.com/pgsty/oink.git themes/oink
git -C themes/oink fetch --tags
git -C themes/oink checkout v1.0.0
git add .gitmodules themes/oink
hugo.yml
theme: oink

CI 必须在运行 Hugo 之前初始化 submodule,否则 themes/oink 是空目录:

git submodule update --init --recursive

离线归档

网络隔离环境使用。两条路径,都先在联网机器上准备,再整体搬入。

hugo mod vendor:把已解析的主题源码固化进站点目录,之后构建既不联网也不需要 Go。

hugo mod vendor          # 生成 _vendor/,里面是主题的完整源码树
tar czf my-docs.tgz .    # 连 _vendor/ 一起搬进隔离环境

_vendor/ 存在时 Hugo 优先使用它(hugo mod graph 输出 +vendor),hugo.yml 里的 module.imports 保持不变。这一步需要 Go,之后的构建不需要。升级主题要回到联网环境重新执行 hugo mod gethugo mod vendor

_vendor/ 只收主题挂载出来的目录(assets data i18n layouts static)以及 hugo.yamltheme.toml,不含 LICENSENOTICEVENDOR.json。要对外分发这份归档,把这三个文件从主题仓库一并取来。

用 tag 源码归档:不建 Go 模块,直接把某个版本的主题解压到 themes/oink/

curl -L -o oink.tar.gz \
  https://github.com/pgsty/oink/archive/refs/tags/v1.0.0.tar.gz
mkdir -p themes/oink
tar xzf oink.tar.gz -C themes/oink --strip-components=1
hugo.yml
theme: oink

主题仓库的根目录就是模块根目录,解压出来直接是 layouts/assets/i18n/static/ 这一层,不需要再进入下一级。重新分发时必须保留 LICENSENOTICEVENDOR.json。最后一个记录了每个第三方运行时的版本、来源、许可证路径与 SHA-256,是离线审计的依据。

跨机器传输时,在联网侧从不可变标签生成归档与校验值:

git clone --branch v1.0.0 --depth 1 \
  https://github.com/pgsty/oink.git oink
git -C oink archive --format=tar.gz --prefix=oink/ \
  --output=../oink-v1.0.0.tar.gz v1.0.0
shasum -a 256 oink-v1.0.0.tar.gz \
  > oink-v1.0.0.tar.gz.sha256

把归档与 .sha256 一起传入隔离环境,先校验再解压:

shasum -a 256 -c oink-v1.0.0.tar.gz.sha256
mkdir -p themes
tar -xzf oink-v1.0.0.tar.gz -C themes

这样得到的归档是自建产物,不是项目发行物。某个标签的发行页面是否附带归档与校验文件按发布而定,使用公开附件时独立验证其校验值。

断网构建之前确认归档内容完整,这十一项都要在:

themes/oink/

  • oink/
    • go.mod模块路径声明,Hugo Module 方式解析用
    • hugo.yaml主题默认参数与 Hugo 版本下限
    • theme.toml主题元数据,theme: oink 方式需要
    • LICENSEApache-2.0
    • NOTICE上游署名,再分发时必须保留
    • VENDOR.json第三方运行时清单:版本、来源、许可证路径、SHA-256
    • assets/SCSS、JS 与随主题分发的第三方运行时
    • layouts/模板、partial、shortcode、render hook
    • static/字体文件,原样发布
    • i18n/32 份界面语言文件
    • data/页尾出处行用的 SPDX 许可证表

固定版本克隆

托管平台要求构建输入包含完整主题树时用:

git clone https://github.com/pgsty/oink.git themes/oink
git -C themes/oink checkout v1.0.0

与 submodule 的区别是主题文件直接进入你的仓库历史,没有 .gitmodules 这层间接。记录最终解析出的 commit 与恢复流程。

四种方式对比

方式 需要 Go 版本可审计 主题源码进你的仓库 适用
Hugo Module go.sum 自动校验 默认推荐
Git submodule 仓库记录 commit 以引用形式 需要主题源码在库内
离线归档 手工核对 checksum 网络隔离
固定版本克隆 需自行记录 平台要求完整树
消费站点不需要前端工具链

Bootstrap、Font Awesome、字体、搜索与图表运行时全部随主题分发。站点不需要 node_modules、PostCSS、RTLCSS,也不需要 CDN。为 Docsy 站点安装 npm 依赖的教程属于上游 Docsy 的流程,不适用于 OINK。

用本地主题 checkout 开发

同时修改主题与站点时才需要这一节。把两个仓库克隆为同级目录:

同级目录布局
~/pgsty/
├── oink/            # 主题
└── product-docs/    # 你的站点

用环境变量 HUGO_MODULE_REPLACEMENTS 把模块临时替换为本地 checkout,go.mod 不变:

cd ~/pgsty/product-docs
HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> ../oink' hugo server

文档站仓库的 Makefile 就是这几条命令的别名,make devmake check 要求主题 checkout 在同级目录 ../oink

Makefile:文档站里的写法
build:
	hugo --cleanDestinationDir --minify

check:
	HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> $(abspath ../oink)' npm test

dev:
	HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> $(abspath ../oink)' hugo server --renderToMemory

Go workspace(go work init + HUGO_MODULE_WORKSPACE=go.work)是等价的另一种做法。两种做法都只作用于本机:CI 与生产构建用的是 go.mod 里的版本,go.work 不要提交。

验证

hugo mod graph                                       # 主题实际解析到哪一版
hugo --gc --minify --printPathWarnings --panicOnWarning

构建以 Total in … 结束、没有 WARN / ERROR 即通过。再确认:

  • /docs/ 打得开,侧栏里有你写的页面
  • 顶栏有搜索框,搜得到刚写的标题
  • 深浅色切换按钮在,切换后代码块配色跟着变(说明 markup.highlight.noClasses: false 生效)
  • git status 里有 go.modgo.sum,没有 public/resources/