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.
| File | Trigger | Purpose |
|---|---|---|
deploy-pages.yml | Push or PR to main (documentation paths) | Build the documentation site and deploy it to GitHub Pages. |
package-vim.yml | Push or PR to main (Vim paths) | Package the Vim configuration and plugins as vim.tar.gz. |
package-nvim.yml | Push or PR to main (Neovim paths) | Package .config/nvim as nvim.tar.gz. |
release.yml | Push a v* tag | Publish a release with notes from CHANGELOG.md. |
deploy-pages.ymlInstalls 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:
public/, under github/ for .github/;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.ymlPackages 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.ymlPackages .config/nvim as an nvim-package artifact when that configuration changes.
release.ymlWhen a v* tag is pushed, the workflow:
scripts/ci/extract_release_notes.sh to pull that version's section out of CHANGELOG.md;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".
scripts/ci/)| Script | Used by |
|---|---|
build_pages.sh | deploy-pages.yml, GitLab pages, local post-commit hook |
extract_release_notes.sh | release.yml, GitLab create_release |
gen_pages.py | build_pages.sh |
Both shell scripts use POSIX sh, so they run on minimal CI images such as GitLab's Alpine image.
The runners provide the tokens used by these workflows; no custom secrets or variables are needed.
| Name | Used by | Source | Manual setup |
|---|---|---|---|
GITHUB_TOKEN | release.yml | Auto-injected by GitHub Actions and the Gitea runner | None |
OIDC token (id-token: write) | deploy-pages.yml | Auto-issued by GitHub for Pages | None |
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):
deploy-pages.yml: Settings → Pages → Build and deployment → Source: GitHub Actions. The job deploys to the github-pages environment.GITHUB_TOKEN with write access to the current repository, which is enough to create releases. No secret needs to be added.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.