CI Workflows

This repository is hosted on GitHub, GitLab (.gitlab-ci.yml), and a self-hosted Gitea instance. Workflows that share CI logic use scripts from scripts/ci/.

Note

A Gitea Actions runner also scans .github/workflows, so these files run on Gitea too unless explicitly scoped. See Multi-forge notes.

Workflows

FileTriggerPurpose
deploy-pages.ymlPush or PR to main (documentation paths)Build the documentation site and deploy it to GitHub Pages.
package-vim.ymlPush or PR to main (Vim paths)Package the Vim configuration and plugins as vim.tar.gz.
package-nvim.ymlPush or PR to main (Neovim paths)Package .config/nvim as nvim.tar.gz.
release.ymlPush a v* tagPublish a release with notes from CHANGELOG.md.

deploy-pages.yml

Installs markdown and runs scripts/ci/build_pages.sh. The script uses gen_pages.py to render every Markdown file tracked by Git into public/. Each run uploads the artifact; only runs on main deploy it, and any change to a .md file triggers the workflow.

Each page keeps its source path (dist/arch/install.md becomes dist/arch/install.md/index.html), except README.md, which becomes the site index, and .github/, which is published as github/ because actions/upload-pages-artifact drops .github from the artifact. gen_pages.py rewrites relative links so they work on the site:

  • a link to a Markdown file goes to its page, query and anchor included;
  • a linked image is copied into public/, under github/ for .github/;
  • a link to any other repository file or directory goes to the forge's file browser at the built commit (GITHUB_SHA here, CI_COMMIT_SHA on GitLab). The local preview links to the file in the checkout instead.

Only href and src attributes of real <a>, <img> and <source> tags are rewritten, so tags shown as code stay as written. A page's title is its navigation label, else its first heading, else its path.

A link whose target does not exist fails the build: gen_pages.py lists the broken links on stderr and exits 1, which stops the Pages jobs. The test-gen-pages pre-commit hook builds the site on every Markdown change and catches them before the commit. Each build also removes the files a previous build left in public/ for renamed or deleted pages.

gen_pages.py is the single source for the page list. The local post-commit preview hook (see .pre-commit-config.yaml) and GitLab pages job use the same script.

package-vim.yml

Packages the Vim configuration as a vim-package artifact, with the plugins pinned in dotfiles_vim_pack_plugins. scripts/ci/fetch_vim_plugins.py reads that list from the role defaults and clones each plugin under .vim/pack/dotfiles/start/ without its .git directory.

package-nvim.yml

Packages .config/nvim as an nvim-package artifact when that configuration changes.

release.yml

When a v* tag is pushed, the workflow:

  1. runs scripts/ci/extract_release_notes.sh to pull that version's section out of CHANGELOG.md;
  2. creates the release via the REST API (POST $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases) using jq + curl.

The same API request works on GitHub and Gitea. Both runners provide GITHUB_API_URL, GITHUB_REPOSITORY, and secrets.GITHUB_TOKEN, allowing the workflow to publish releases on either forge.

Important

The release notes are read from CHANGELOG.md at the tagged commit. Add the ## [vX.Y.Z] section before pushing the tag. Without it, the release uses the placeholder "No specific notes found".

Shared scripts (scripts/ci/)

ScriptUsed by
build_pages.shdeploy-pages.yml, GitLab pages, local post-commit hook
extract_release_notes.shrelease.yml, GitLab create_release
gen_pages.pybuild_pages.sh

Both shell scripts use POSIX sh, so they run on minimal CI images such as GitLab's Alpine image.

Variables & secrets

The runners provide the tokens used by these workflows; no custom secrets or variables are needed.

NameUsed bySourceManual setup
GITHUB_TOKENrelease.ymlAuto-injected by GitHub Actions and the Gitea runnerNone
OIDC token (id-token: write)deploy-pages.ymlAuto-issued by GitHub for PagesNone

Note

release.yml declares permissions: contents: write to let the automatic token create releases. This permission is set in the workflow, not in the repository settings.

One-time setup prerequisites (not variables):

  • GitHub Pages must be enabled for deploy-pages.yml: Settings → Pages → Build and deployment → Source: GitHub Actions. The job deploys to the github-pages environment.
  • Gitea: Enable Actions for the repository. The runner injects GITHUB_TOKEN with write access to the current repository, which is enough to create releases. No secret needs to be added.

Multi-forge notes

Because Gitea's runner also picks up .github/workflows:

  • release.yml is intentionally forge-agnostic and runs on both GitHub and Gitea.
  • deploy-pages.yml, package-vim.yml, and package-nvim.yml use GitHub-specific features (Pages or artifact upload), so their jobs are guarded with:
if: ${{ github.server_url == 'https://github.com' }}

On Gitea, github.server_url is the instance URL and those jobs skip. On GitHub, they run normally.