Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Quick start

Clone the OINK documentation site, preview it locally, replace the site details, and deploy to GitHub Pages.

This path does not start from an empty directory. It starts by cloning the site you are reading, deleting what you do not need, and replacing the rest with your own details. This site is OINK’s regression site: it contains every component and every page type, and it tracks the theme version. Trimming it down is less writing than adding configuration and examples one at a time to an empty directory.

Prerequisites: a machine that can install Hugo Extended and Go, a GitHub account, and ten minutes. No Node.js and no other front-end toolchain.

What you end up with

A bilingual documentation site: your directory tree in the left sidebar, this page’s outline on the right, full-text search and a command palette in the navbar, and light or dark following the system. One Markdown source produces the web page, the print page, plain Markdown and RSS. Hosted on GitHub Pages.

How content, configuration and the theme combine into a static site at build time
One source, four outputs: HTML, print, Markdown, RSS

Walkthrough

  1. Install Hugo Extended and Go

    Besides Git you need two things. Hugo Extended must be 0.160.1 or newer: the standard Hugo build has no embedded Sass compiler, cannot compile the theme’s styles, and fails the build. Go resolves modules: OINK is published as a Hugo Module, and Hugo uses Go’s module machinery to download and verify github.com/pgsty/oink.

    macOS
    brew install hugo go git
    Linux
    # Distribution repositories often carry a Hugo that is too old; use the official deb (this site's CI does the same)
    curl -LO https://github.com/gohugoio/hugo/releases/download/v0.164.0/hugo_extended_0.164.0_linux-amd64.deb
    sudo dpkg -i hugo_extended_0.164.0_linux-amd64.deb
    sudo apt install -y golang-go git
    Windows
    winget install Hugo.Hugo.Extended
    winget install GoLang.Go
    winget install Git.Git

    Check once afterwards; the output must contain extended:

    $ hugo version
    hugo v0.164.0+extended+withdeploy darwin/arm64 BuildDate=2026-07-06T16:39:30Z
    $ go version
    go version go1.26.6 darwin/arm64
    

    On other platforms follow the Hugo installation guide and go.dev/dl, taking care to pick the extended build.

  2. Clone the documentation site and preview it

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

    Open http://localhost:1313/; the Chinese site is at http://localhost:1313/zh/. The first start downloads the theme module (a few seconds to a minute, depending on the network); after that, edits hot-reload in milliseconds.

    The committed go.mod pins the theme version, so the clone builds as it is, with no extra install script.

    Note

    The Makefile in the repository is only a set of command aliases. make dev and make check use a sibling ../oink theme checkout through HUGO_MODULE_REPLACEMENTS; make build and make serve always use the published version pinned in go.mod. For a new site, hugo server is enough.

  3. Replace the site details

    Site identity lives entirely in hugo.yml. baseURL is a YAML anchor — the real address is written on params.productionURL, and that is the only place to change it:

    hugo.yml
    title: Product Docs # navbar site name and <title>
    
    params:
      productionURL: &productionURL https://docs.example.com/
      github_repo: https://github.com/example/product-docs # where "Edit this page" points
      copyright:
        authors: '[Example Inc.](https://example.com/)'
        from_year: 2026
      footer_center_info: ''
    
    baseURL: *productionURL

    languages.en.title and languages.zh.title override the top-level title, so change both. Every parameter’s meaning and default is in Configuration.

    Delete the configuration specific to this site. Keeping it points your site at OINK’s repositories and accounts.

    Key in hugo.yml What to do
    services.googleAnalytics.id OINK’s analytics ID. Delete it, or replace it with your own when you want analytics
    params.comments giscus pointing at discussions in pgsty/oink.pgsty.com. Delete the block or point it at your repository
    params.tdVersion params.version params.version_menu params.versions OINK’s version menu. Delete
    params.github_project_repo A link to the theme repository. Delete
    languages.<lang>.menus.main Navbar entries pointing at this site’s sections such as /docs/tutorial. Rewrite for your tree

    Replace the logo and icons. Replace these three files, keeping the filenames — the theme mounts them by name:

    static/
    static/favicon.svg           # browser tab icon
    static/favicon.ico
    static/apple-touch-icon.png  # iOS add-to-home-screen

    static/logo.svg is this site’s own brand lockup and no parameter points at it. Delete it, or replace it with a horizontal wordmark and set params.wordmark.

    Replace the content. content/docs/ is OINK’s own theme documentation; delete the whole tree and write your first page:

    rm -rf content/docs && mkdir -p content/docs
    content/docs/_index.md
    ---
    title: Docs
    linkTitle: Docs
    description: Product documentation.
    weight: 20
    ---
    
    Everything about running Product in production.

    content/blog/ can keep one post as a template or be deleted entirely (if you delete it, remove the blog entry from menus.main as well). Which directories must stay and which belong to the documentation site itself is in Repository tour.

    For an English-only site, delete the whole languages.zh block and every .zh.md file, leaving one language:

    hugo.yml
    defaultContentLanguage: en
    languages:
      en:
        label: English
        locale: en-US
        weight: 1
        title: Product Docs
        menus:
          main:
            - { name: Docs, pageRef: /docs, weight: 20 }
    find content -name '*.zh.md' -delete

    To keep both languages or swap in a different pair, see Languages.

  4. Deploy

    Create an empty repository on GitHub and replace the local history with your own:

    rm -rf .git && git init -b main
    git add . && git commit -m "Initial documentation site"
    git remote add origin [email protected]:example/product-docs.git
    git push -u origin main

    The repository ships with .github/workflows/pages.yml: a push to main builds and publishes, and it can also be triggered by hand from the Actions page (workflow_dispatch). It pins the Hugo Extended and Go versions, builds with --printPathWarnings --panicOnWarning, and takes baseURL from GitHub Pages, so publishing to a subpath such as example.github.io/product-docs/ needs no configuration change.

    In the repository, go to Settings → Pages → Build and deployment → Source and choose GitHub Actions. The default is Deploy from a branch, and leaving it will make the workflow fail at the deploy step.

    Deleting scripts/ means editing the workflow

    The Verify advertised and pinned release match step in pages.yml runs node scripts/check-release-pin.mjs to check that the version the site advertises matches the one pinned in go.mod. Once scripts/ is gone, remove that step and Set up Node.js from pages.yml.

    For Cloudflare Pages, Netlify, Nginx and offline packaging, see Deploy: the build command is always hugo --gc --minify, and only baseURL and the environment variables differ.

Verify

Run a production build locally. It is stricter than the development server, and path warnings fail the build:

hugo --gc --minify --printPathWarnings --panicOnWarning

It passes when it prints Total in … with no WARN or ERROR. Then check against the preview:

  • The navbar shows your site name and logo, and the browser tab shows your favicon
  • The sidebar is your own tree, and every page opens
  • Ctrl with K (or with K on macOS) opens the command palette and finds the page you just wrote
  • In the menu beside the page title, “Edit this page” points at your repository, not pgsty/oink.pgsty.com
  • After deploying, Deploy Oink site to GitHub Pages is green on the repository’s Actions page

For build errors, see Troubleshooting.

Next steps

  • Repository tour — what each cloned directory is, and what can go.
  • Writing pages — what a documentation page is made of: front matter, heading anchors, links and images.
  • Components — callouts, tabs, field lists, file trees and the rest, one page each.
  • Brand and appearance — accent colour, font preset, page width and custom styles.
  • Deploy — hosting beyond GitHub Pages, and the acceptance checklist.

Instructions for a coding assistant

The four steps above can be handed to a coding assistant (Claude Code, Codex and the like). Copy the block below and replace the three bracketed items with your own details:

Instructions to copy wholesale
Please set up a documentation site for me with the OINK theme, following the process
below. Where something is unclear, apply "only ask when information is missing".

1. Check the environment: run `hugo version` and require the output to contain `extended`
   with a version >= 0.160.1; run `go version` and require a version number. If either
   fails, install it per the official documentation first — `brew install hugo go` on
   macOS, the hugo_extended deb from GitHub Releases on Debian/Ubuntu.
2. Clone the site template: `git clone https://github.com/pgsty/oink.pgsty.com [target directory]`
   and change into it.
3. Change three things in hugo.yml: the top-level `title` and `languages.<lang>.title` to
   [site name]; `params.productionURL` to [site domain] (baseURL is a YAML anchor pointing
   at it, so do not change baseURL separately); `params.github_repo` to the repository this
   site will live in. Also delete this site-specific configuration:
   `services.googleAnalytics`, `params.comments`, `params.tdVersion`, `params.version`,
   `params.version_menu`, `params.versions`, `params.github_project_repo`, and reduce
   `menus.main` to /docs and /blog only.
4. Clear the example content: delete the whole `content/docs/` tree and create a new
   `content/docs/_index.md` (front matter with at least title / description / weight);
   keep one post under `content/blog/` as a template. Delete the scaffolding that belongs
   to the documentation site itself: `tests/`, `scripts/`, `playwright.config.mjs`,
   `package.json`, `package-lock.json`, `AGENTS.md`, `TRANSLATION.md`, `CONTRIBUTING.md`,
   `agent-docs.config.yml`; delete every workflow under `.github/workflows/` except
   `pages.yml`; and remove the `Set up Node.js` and
   `Verify advertised and pinned release match` steps from `pages.yml`.
5. Start `hugo server` in the background and confirm http://localhost:1313/ returns 200
   and the page title is the new site name.
6. Validate: run `hugo --gc --minify --printPathWarnings --panicOnWarning` and require it
   to end with `Total in ...` and no WARN/ERROR. Fix any error rather than working around
   it by suppressing warnings.
7. Only ask me when [site name], [site domain] or the repository address is missing;
   otherwise proceed with the defaults above.

The resulting site is easy for an assistant to read: every page has a .md plain-text output, the site root has llms.txt, and the menu beside the page title offers “Copy as Markdown” and “Open in Claude”. See AI-agent support.

To start from an empty directory instead of this repository, see From scratch and other install methods.

1 - Repository tour

What every cloned directory is — what must stay, what to replace with your own details, and what belongs to the documentation site and can go entirely.

This page goes through every file and directory of a pgsty/oink.pgsty.com clone: what must stay, what to replace with your own details, what belongs to the documentation site and can be deleted whole — plus a safe order to delete in.

The theme’s code is not in this repository. It is a Hugo Module pinned by go.mod and stored in Go’s module cache. This repository holds content, configuration, and a small number of site-level overrides.

Top-level structure

the cloned my-docs/

  • my-docs/
    • hugo.ymlthe site's only configuration: identity, languages, menus, params, module imports
    • go.modpins the theme version
    • go.sumchecksums for the theme module
    • content/all content; the directory structure is the sidebar structure
      • _index.mdhome page; _index.zh.md is its Chinese counterpart
      • search.mdresults page for Google Programmable Search; delete if unused
      • docs/documentation tree: OINK's own theme documentation
      • blog/blog: engineering notes and release announcements
    • assets/resources that go through Hugo processing
      • scss/site style overrides, three partials
      • images/images that need resizing or cropping
      • parts/Markdown and YAML fragments pulled in by the include shortcode
    • static/copied to the site root as is, unprocessed
      • logo.svgbrand lockup; no parameter points at it
      • favicon.svgbrowser tab icon
      • favicon.ico
      • apple-touch-icon.pngiOS add-to-home-screen
      • images/screenshots and diagrams
    • layouts/site template overrides: override the narrowest one
      • _shortcodes/the site's own shortcodes
    • data/data-driven pages
      • home/home page sections: en.yaml / zh.yaml
      • landing/landing page data
      • download/release and download page data
    • .github/
      • workflows/pages.yml deploys; the other two are this site's regression tests
    • tests/documentation site only: Playwright, goldens, build assertions
      • browser/Playwright specs
      • hugo-build/build assertions
      • md-output/Markdown output goldens
      • alt-site/alternate-configuration builds
      • favicons/
      • release-pin/
      • fixtures/
    • scripts/documentation site only: translation parity and link checks
      • check-doc-translations.mjs
      • check-markdown-style.mjs
      • check-rendered-links.mjs
      • check-rendered-markdown.mjs
      • check-release-pin.mjs
    • Makefilebuild / serve call Hugo directly; dev / check point at a sibling ../oink
    • package.jsonthe test toolchain; not used to build the site
    • package-lock.json
    • playwright.config.mjs
    • agent-docs.config.ymlconfiguration for the agent-documentation scoring tool
    • AGENTS.mdrepository notes for coding agents
    • TRANSLATION.mdthe bilingual translation process
    • CONTRIBUTING.md
    • README.md
    • LICENSEApache-2.0, for the site code
    • LICENSE-CC-BY-4.0content licence
    • NOTICE

Not listed above: .gitignore, .gitattributes, .nvmrc, .npmrc, and the generated output excluded by .gitignorepublic/ (build output), resources/ (Hugo’s resource cache) and node_modules/. That last group never enters version control.

There is no i18n/ in the repository: interface strings (“Previous”, “On this page” and the like) come from the theme’s 32 language files. To change one of them, create i18n/zh.yaml at the site root and write only the keys you override.

What to do with each entry

Path What it is What to do after forking
hugo.yml The site’s only configuration file — no config/ directory and no per-environment overrides Replace with your details: identity, languages, menus, brand
go.mod go.sum Pin the theme version and record its checksums Must stay, and both are committed
content/ All content; the directory structure decides the sidebar structure Must stay; replace the docs/ and blog/ inside it with your own
content/search.md A full-page search results page (layout: search); it only has content when Google Programmable Search is configured (params.gcs_engine_id) Delete it when using the theme’s local search
assets/scss/ Site style overrides (_variables_project.scss and friends) Keep it to change colours and fonts; empty it if you change neither
assets/images/ Images that need Hugo processing (resize, crop) Replace with your own
assets/parts/ Fragments the include shortcode pulls in Replace or delete with the pages that use them
static/ Copied to the site root as is Replace with yours: logo, favicon, screenshots
layouts/_shortcodes/ This site’s four shortcodes, none referenced by current content Can be deleted
data/home/ Home page section data (hero, capability panels) Change to yours; delete it and the home page falls back to an ordinary page
data/landing/ data/download/ Data for landing pages and the release/download page Delete if unused
.github/workflows/pages.yml Builds and publishes to GitHub Pages on a push to main Keep, adjusted for your repository
.github/workflows/site-checks.yml browser-quality.yml This site’s regression pipelines Documentation site only; can be deleted
tests/ scripts/ playwright.config.mjs package.json package-lock.json This site’s regression tests and check tooling Documentation site only; can be deleted
Makefile Shortcuts for developing the theme and the site together (expects a sibling ../oink) Documentation site only; can be deleted
AGENTS.md TRANSLATION.md CONTRIBUTING.md agent-docs.config.yml This site’s collaboration conventions Replace with your own, or delete
README.md LICENSE LICENSE-CC-BY-4.0 NOTICE Description and licences Replace with your own
.nvmrc .npmrc Node version and npm configuration Delete along with package.json
Building a site with OINK needs no Node.js

The package.json, tests/ and scripts/ in this repository maintain the documentation site itself. Building your site is one command: hugo --gc --minify.

Deletion order

Delete the periphery first, then the content, then the data. Build after each step so a problem points back at one step.

  1. Drop the scaffolding

    None of this takes part in rendering, and removing it affects no page.

    rm -rf tests scripts node_modules
    rm -f package.json package-lock.json playwright.config.mjs .nvmrc .npmrc
    rm -f AGENTS.md TRANSLATION.md CONTRIBUTING.md agent-docs.config.yml Makefile
    rm -f .github/workflows/site-checks.yml .github/workflows/browser-quality.yml

    After deleting scripts/ you must edit .github/workflows/pages.yml: remove the Set up Node.js and Verify advertised and pinned release match steps, or the deploy fails there.

  2. Drop the example content

    content/docs/ is OINK’s own theme documentation and content/blog/ its engineering blog; neither has anything to do with your product.

    rm -rf content/docs
    mkdir -p content/docs
    rm -rf content/blog        # if you want no blog; otherwise keep one post as a template

    Edit menus.main under each language in hugo.yml at the same time: those entries point at paths such as /docs/tutorial and /blog/release that no longer exist. content/_index.md is the home page — keep it and replace the body with yours.

  3. Trim the data

    The three groups under data/ feed the home page, landing pages and release pages. Keep the home page data and edit it; delete the other two if unused.

    rm -rf data/landing data/download

    data/home/en.yaml and data/home/zh.yaml decide which sections the home page has; each entry is explained in Home and landing pages. Deleting data/home/ entirely still builds, and the home page falls back to an ordinary content page.

  4. Swap the identity

    Finally, change the site name, params.productionURL, params.github_repo and the brand parameters in hugo.yml to yours, replace the logo and favicon under static/, and delete the OINK-specific configuration: services.googleAnalytics, params.comments and the params.version* keys. The itemized list is in step 3 of Quick start.

Where the theme lives

The theme is referenced as a Hugo Module, and two places point at it:

hugo.yml
module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: '0.160.1'
go.mod
require github.com/pgsty/oink v0.6.0

hugo.yml declares which theme to use, go.mod pins which version of it, and go.sum records that version’s checksums. All three files are committed. The theme source never enters your repository: Hugo downloads it into Go’s module cache, and hugo mod graph shows what actually resolved.

Upgrade to the newest version:

hugo mod get -u github.com/pgsty/oink

Pin to one version:

hugo mod get github.com/pgsty/[email protected]

Both commands rewrite go.mod and go.sum. A production site pins a release tag rather than following main. What to check before and after an upgrade, and how to roll back, is in Upgrade.

Site overrides

Files under layouts/ shadow the theme’s files of the same name, following Hugo’s template lookup order. This site has only one kind:

  • layouts/_shortcodes/*.html — the site’s own shortcodes. Product documentation that needs a shortcode with business meaning puts it here too.

Self-linking heading anchors come from the theme’s own _markup/render-heading.html; a site does not need to build that hook.

To change the shell (sidebar, footer, page end), override the narrowest partial rather than copying baseof.html wholesale — a copy has to be merged by hand at every theme upgrade.

Verify

Build after each deletion step so an error points at what you just removed:

hugo --gc --minify --printPathWarnings --panicOnWarning

Once the deleting is done, all of this should hold:

  • The build ends with Total in … and no WARN or ERROR
  • No navbar entry links to a deleted directory
  • Headings still have their self-link anchors (the theme’s own heading render hook; the site needs no override)
  • git status shows no public/ or resources/

2 - From scratch and other install methods

Build a minimal OINK site in an empty directory, and weigh the four install methods — Module, submodule, offline archive, pinned clone.

This page builds a minimal OINK site in an empty directory: a dozen lines of hugo.yml plus one hugo mod get gives a single-language site you can preview. The cost is that the home page, the example content and any component usage to copy from are all yours to write.

An existing Hugo site needs no scaffolding: install the theme module, add the three Goldmark prerequisites (see Writing hugo.yml), and leave the content alone. For an existing Docsy site, see Upgrade.

The second half weighs four install methods: Hugo Module, Git submodule, offline archive, pinned clone.

From an empty directory to the first page

  1. Create the skeleton and fetch the theme

    hugo new site --format yaml my-docs
    cd my-docs
    hugo mod init github.com/example/my-docs
    hugo mod get github.com/pgsty/[email protected]

    What follows hugo mod init is your own site’s module path, usually the repository address. hugo mod get writes go.mod and go.sum, and both are committed.

    The newest version number is on GitHub Releases; the v0.6.0 on this page is what this site currently pins. A production site pins a release tag rather than following main: @latest is a one-off resolution, not a version policy.

  2. Writing hugo.yml

    Rename the hugo.yaml that hugo new site generated to hugo.yml (Hugo accepts both; this documentation uses the latter throughout) and replace its contents with the following, which builds as it stands:

    hugo.yml
    title: Product Docs
    baseURL: https://docs.example.com/
    defaultContentLanguage: en
    # enableGitInfo: true        # the "last modified" time comes from git; run git init before enabling
    
    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 }
    
    # The three Goldmark prerequisites: OINK's native Markdown components depend on them
    markup:
      goldmark:
        renderer:
          unsafe: true # allow inline HTML in content
        parser:
          attribute:
            block: true # attribute lines such as {.steps} {.cards} {caption=}
          wrapStandAloneImageWithinParagraph: false # only a block-level image can carry an attribute line
      highlight:
        noClasses: false # code colours follow light and dark mode
    
    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'

    What each of the five blocks governs:

    Block Governs Consequence of omitting it
    Top level + languages Site name, domain, languages and navbar menu A wrong baseURL sends every absolute link astray in production
    markup.goldmark The three component prerequisites An attribute line becomes a literal {.steps} in the prose
    params Search, repository links, shell switches Interactive features stay off; the theme does not decide for the site
    outputs The per-page .md, llms.txt and print pages No “Copy as Markdown” in the page menu, and no print view
    module References the theme and declares the Hugo floor The build cannot find the theme

    Mathematics additionally needs Goldmark’s passthrough extension; see Math. Every key’s full meaning and default is in Configuration.

  3. Write the first page

    Every top-level directory under content/ is a section, and the directory structure is the sidebar structure. A documentation section needs at least an _index.md:

    content/docs/_index.md
    ---
    title: Docs
    linkTitle: Docs
    description: Everything about running Product in production.
    weight: 20
    ---
    
    Start with [Install](/docs/install/).
    content/docs/install.md
    ---
    title: Install
    description: Install Product on a fresh machine.
    weight: 10
    ---
    
    ## Prerequisites {#prerequisites}
    
    > [!IMPORTANT]
    > Product needs PostgreSQL 18 or newer.
    
    ## Install {#install}
    
    ```bash
    curl -fsSL https://get.example.com | bash
    ```

    Write explicit {#id} anchors on headings: when a translation is added later, the two languages’ anchors have to correspond. How to write a page is in Writing pages.

  4. Preview

    hugo server

    Open http://localhost:1313/ and the sidebar shows Docs → Install. Edits hot-reload in milliseconds.

Other install methods

The steps above use a Hugo Module. The other three address particular constraints: network isolation, a platform that requires the build input to contain the whole theme tree, or an organization that reviews its own copy of the theme. Apart from hugo mod vendor, none of them creates a Go module, and the site references the theme with theme: oink rather than module.imports. The shared cost is that version resolution and integrity checking become your responsibility.

Hugo Module (recommended)

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/[email protected]
hugo.yml
module:
  imports:
    - path: github.com/pgsty/oink

The only method where Hugo resolves the version itself, verifies the checksum, and leaves an audit record in go.sum. hugo mod graph shows what actually resolved and hugo mod get -u upgrades. It needs Go on the machine.

Git submodule

Record an exact theme commit in the site repository:

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

CI must initialize the submodule before running Hugo, or themes/oink is an empty directory:

git submodule update --init --recursive

Offline archive

For network-isolated environments. Two paths, both prepared on a connected machine and carried in whole.

With hugo mod vendor, the resolved theme source is frozen into the site directory, and later builds need neither the network nor Go.

hugo mod vendor          # writes _vendor/, holding the theme's full source tree
tar czf my-docs.tgz .    # carry _vendor/ into the isolated environment with everything else

When _vendor/ exists Hugo prefers it (hugo mod graph prints +vendor), and module.imports in hugo.yml stays as it is. This step needs Go; the builds after it do not. Upgrading the theme means returning to a connected environment and running hugo mod get and hugo mod vendor again.

_vendor/ collects only the directories the theme mounts (assets, data, i18n, layouts, static) plus hugo.yaml and theme.toml. It does not include LICENSE, NOTICE or VENDOR.json. To redistribute that archive, take those three files from the theme repository as well.

With a tag source archive, no Go module is created; a version of the theme is simply unpacked into themes/oink/.

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

The theme repository’s root is the module root, so unpacking lands directly on layouts/, assets/, i18n/ and static/ with no further level to descend into. Redistribution must keep LICENSE, NOTICE and VENDOR.json; the last records each third-party runtime’s version, source, licence path and SHA-256, and is what an offline audit rests on.

When moving between machines, generate the archive and its checksum from an immutable tag on the connected side:

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

Carry the archive and its .sha256 into the isolated environment, verify, then unpack:

shasum -a 256 -c oink-v0.6.0.tar.gz.sha256
mkdir -p themes
tar -xzf oink-v0.6.0.tar.gz -C themes

An archive produced this way is your own artifact, not a project release. Whether a given tag’s release page carries an archive and a checksum file varies by release; verify the checksum independently when using a public attachment.

Before building offline, confirm the archive is complete. All eleven of these must be present:

themes/oink/

  • oink/
    • go.modmodule path declaration, used when resolving as a Hugo Module
    • hugo.yamltheme default parameters and the Hugo version floor
    • theme.tomltheme metadata, required by the theme: oink method
    • LICENSEApache-2.0
    • NOTICEupstream attribution; must be kept on redistribution
    • VENDOR.jsonthird-party runtime manifest: version, source, licence path, SHA-256
    • assets/SCSS, JS and the third-party runtimes shipped with the theme
    • layouts/templates, partials, shortcodes, render hooks
    • static/font files, published as is
    • i18n/32 interface language files
    • data/the SPDX licence table behind the page-end attribution line

Pinned clone

For a hosting platform that requires the build input to contain the whole theme tree:

git clone https://github.com/pgsty/oink.git themes/oink
git -C themes/oink checkout v0.6.0

The difference from a submodule is that the theme files enter your repository history directly, without the .gitmodules indirection. Record the commit that was finally resolved and the procedure for restoring it.

The four methods compared

Method Needs Go Version auditable Theme source in your repository Use when
Hugo Module Yes go.sum verifies automatically No The default
Git submodule No The repository records the commit By reference The theme source has to be in the repository
Offline archive No Checksums verified by hand Yes Network isolation
Pinned clone No You record it yourself Yes The platform requires a complete tree
A consuming site needs no front-end toolchain

Bootstrap, Font Awesome, the fonts, and the search and diagram runtimes all ship with the theme. A site needs no node_modules, no PostCSS, no RTLCSS and no CDN. Tutorials that install npm dependencies for a Docsy site describe upstream Docsy’s process and do not apply to OINK.

Developing against a local theme checkout

This section applies only when changing the theme and the site together. Clone the two repositories as siblings:

sibling directory layout
~/pgsty/
├── oink/            # the theme
└── product-docs/    # your site

Use the HUGO_MODULE_REPLACEMENTS environment variable to substitute the local checkout temporarily, leaving go.mod untouched:

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

The documentation site’s Makefile is an alias for exactly these commands, and make dev and make check expect the theme checkout at the sibling ../oink:

Makefile: as the documentation site writes it
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

A Go workspace (go work init plus HUGO_MODULE_WORKSPACE=go.work) is an equivalent alternative. Both apply to the local machine only: CI and production builds use the version in go.mod, and go.work is never committed.

Verify

hugo mod graph                                       # which theme version actually resolved
hugo --gc --minify --printPathWarnings --panicOnWarning

It passes when the build ends with Total in … and no WARN or ERROR. Then confirm:

  • /docs/ opens and the sidebar holds the page you wrote
  • The navbar has a search box that finds the heading you just wrote
  • The light/dark toggle is present, and code block colours follow it (which shows markup.highlight.noClasses: false took effect)
  • git status shows go.mod and go.sum, and no public/ or resources/
  • Quick start — the other path: clone the documentation site and trim it
  • Repository tour — what each directory of the documentation site is
  • Configuration — every hugo.yml key and its default
  • Writing pages — how to keep writing after the first page
  • Upgrade — upgrading the theme module, and migrating from Docsy