Self-hosted service
tuna/mirror-web avatar
tuna/mirror-web

tuna mirror-web: the Jekyll and Vue source behind the TUNA mirror site

Source code of the web interface of https://mirrors.tuna.tsinghua.edu.cn/

890 stars317 forksJavaScriptGPL-2.0

At a glance

What is it?
mirror-web is the source of the web interface of the TUNA mirror site, a Jekyll build with a Vue and TypeScript front end compiled by Vite, a Zhelp system for multilingual documentation, and an NGINX fancy-index for directory listings. It suits anyone running their own mirror or wanting a bilingual documentation system on Jekyll.
Who is it for?
mirror-web is a good fit for a team standing up an open source mirror site and wanting a proven front end with a working directory listing, a bilingual help system and a Docker build already wired up. It is the wrong tool if you want a general purpose static site generator, since the structure is built around mirrors, the genisolist configuration and the mirrorz ecosystem, and it is the wrong tool if you cannot invest the time to strip and rebrand it.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A mirror site front end, not a general purpose generator

The scope is narrower than the technology stack suggests. This repository holds the source code of the web interface of the TUNA mirror site, and the whole thing is organised around the idea of a mirror: a set of software repositories that people want to fetch quickly from somewhere near them. The configuration surface in the readme makes that obvious. There is a Jekyll config file holding the site name, description and links, and a data file with the short descriptions of each mirror and the configuration for the special cases among them, and the readme asks you to imitate the existing entries when you add your own. Then there is the genisolist configuration, which generates the direct download links, and the readme is specific that changes to it go to the separate genisolist repository rather than here. So the site is a shell around a set of per-mirror entries and a link generator, and the value of the repository is that both of those are already solved. The readme also opens with a rebranding condition that anyone reusing this must satisfy, and that condition shapes everything else about how you would adopt it.

Rebranding is a requirement, not a suggestion

The first section of the readme is a set of conditions in bold, and it is worth reading as a contract rather than as advice. Anyone who uses this project to stand up an open source mirror site must remove all content related to Tsinghua University and TUNA, including and not limited to the site name, the logo and all related text and graphics appearing in the various documents. They must credit the project source appropriately on the site home page, and they must release the modified source code following the GPLv2 licence. That third condition is the one with teeth, because it means the fork is itself a redistribution and the copyleft obligation follows the derived work. The readme frames the whole thing as a fork-and-rebrand exercise rather than a template, and the licensing file at the root is GPL-2.0. So the realistic adoption path is a fork with the branding replaced, the source published, and the mirror list rebuilt, and anyone considering it should be clear that the GPLv2 obligation applies to the result. The readme also asks contributors to be sent to the upstream mirrorz documentation repository when a change applies to every mirrorz site, rather than duplicating it locally.

Jekyll is the only entry point, and Vite runs inside it

The build is a single pipeline with two toolchains, and the documentation describes the order precisely because the order matters. Jekyll reads the config and the pages to render and reads the data files, then calls the Zhelp plugin, which is registered at the highest priority so that it runs before the other generators including Vite. That plugin reads the language and collection tag parameters from the config, calls a Node script through the tsx ESM loader with the output directory, the language and the site configuration, and that script reads the list of enabled pages and loads files for each page with local taking priority over global, merging the two with local overriding global. Markdown is parsed with markdown-it and the MyST plugin, which handles the ztmpl directive and role, and the templates are compiled with Hogan.js while interactive forms are rendered through React server side rendering and code blocks are highlighted with highlight.js. The output is Jekyll-compatible Markdown with front matter written into a cache directory, and the plugin then registers that directory as the help collection. After that, Jekyll calls its Vite plugin, which encodes the data to pass to Vite as JSON in a temporary file, runs the Vite command, and registers the resulting files as static files. Only then does Jekyll render the pages with Liquid, using the manifest to emit the correct script references.

Building it locally needs two toolchains and some downloaded data

Some dynamic data files have to be downloaded before the site will run, which is the first thing a new contributor hits:

bash
wget https://mirrors.tuna.tsinghua.edu.cn/static/tunasync.json -O static/tunasync.json
mkdir -p static/status
wget https://mirrors.tuna.tsinghua.edu.cn/static/status/isoinfo.json -O static/status/isoinfo.json

Then you need Ruby 3.0 or newer and Node.js 18 or newer, and because part of the documentation is embedded as a submodule you have to initialise the submodules after cloning, with the readme noting that a git submodule update --init is required before building. The build commands are the standard pair:

bash
bundle install --deployment
npm install
bundle exec jekyll build
bundle exec jekyll serve --livereload

Two environment variables change the build. Setting JEKYLL_ENV to production produces a minified build and the readme warns it can take a while. Setting VISUALIZER to true writes a Rollup bundle size analysis to a stats file. The dependency set in package.json explains a lot of the build configuration. The Node requirement is a version 22.15 or newer or 23.5 and above, and the engines field says so. There is a legacy Vite plugin and a Babel preset for njs, which is how the front end is compiled twice, once as a modern bundle and once as a legacy bundle that runs in the NGINX njs module, and the legacy output is transpiled with Babel while recording the polyfills it needs, with a set of individual polyfill packages for promises, events, form data and elements. The preinstall and postinstall scripts also apply a patch package directory, so a plain npm install is not quite what it looks like.

Docker builds, and the front end has to run in njs

A Dockerfile.build is provided and the readme recommends it for production deployments, because the build is otherwise a two toolchain affair. Building and running are two commands:

bash
docker build -t tunathu/mirror-web -f Dockerfile.build .
bash
docker run --rm -v /path/to/mirror-web:/data tunathu/mirror-web

The run command mounts the local directory into the container, and the built site ends up in the site subdirectory of that mount. The interesting constraint is that the front end has to work in the NGINX njs module, which is why the legacy bundle exists at all and why the build produces two outputs, with the njs-targeted code transpiled with Babel specifically so it runs there. That is a genuine constraint on what the front end can use, and it explains several of the dependencies in the package manifest, including the web component template package, the polyfills and the njs type definitions. The second stage of the Docker build copies only the built output, so the image contains the site and nothing else, and there is a separate workflow for the published images.

fancy-index turns a directory listing into a page

The directory listing, which is most of what a mirror site actually is, is handled by the NGINX fancy-index module with JavaScript injected through the njs module, and the readme gives the full configuration. The requirements are that NGINX has the fancyindex and js modules installed and that the js module is at least version 0.7.9. The server block needs a js_path directive pointing at the built njs output, and then a set of fancyindex settings. Some are marked as not to be changed, because they have to line up with the front end code, and they cover the time format, whether the path is shown, and the header and footer fragments that come from the fancy-index directory in the repository. The rest are recommendations, including turning off exact size, setting the name length to 256, and a location block for the fancy-index path marked internal that imports the render function from the njs file and points two sublocations at the before and after render functions. Then any location that should show a directory listing needs the fancyindex directive turned on. The before and after render hooks are the interesting part, because that is how the listing is made to look like the site rather than like a default index page.

A documentation system built for two levels of content

The help pages are managed by a system the project calls Zhelp, and its two-level structure is the part worth understanding. Global documentation lives in a directory pulled in as a git submodule from the mirrorz documentation repository and contains the help content common to every mirrorz mirror site, while local documentation lives in this repository and holds site specific configuration and overrides. The readme is explicit about the workflow: if your change applies to all mirrorz mirror sites, send a pull request upstream, and if it is specific to TUNA, send it here. During the build, files in the local directory override the same-named files in the global one, so a site can adjust the common help without forking it. Each help page is a directory with a YAML configuration file and a default content block in Markdown, and the YAML holds the page title, the form input variable definitions and the list of content blocks. A new page is enabled by adding its name to the enabled YAML file. The content is parsed with markdown-it and the MyST plugin, which supports a ztmpl directive for embedding interactive template blocks using Mustache syntax compiled by Hogan.js and rendered client side from the user's form choices, with options for the language, the associated input variables, a global flag and a path hint.

Editorial conclusion

mirror-web is a good fit for a team standing up an open source mirror site and wanting a proven front end with a working directory listing, a bilingual help system and a Docker build already wired up. It is the wrong tool if you want a general purpose static site generator, since the structure is built around mirrors, the genisolist configuration and the mirrorz ecosystem, and it is the wrong tool if you cannot invest the time to strip and rebrand it. Before using it, read the rebranding requirement at the top of the readme, which is not optional: anyone deploying this must remove the TUNA and Tsinghua naming, logos and text, credit the project source on the home page, and release the modified source under GPLv2. The licence is GPL-2.0, the last push was on 2026-10-16, and the repository publishes no GitHub releases.

Frequently asked questions

What is the tuna mirror-web repository?

It is the source code of the web interface of the TUNA mirror site, a Jekyll static site with a Vue, TypeScript and Vite front end, a Zhelp system for multilingual documentation, an NGINX fancy-index for directory listings, and Docker builds. Configuration lives in a Jekyll config file, a data file with per-mirror descriptions, and a genisolist file for direct download links.

What must I do if I use mirror-web to run my own mirror site?

The readme states it as a requirement: remove all content related to Tsinghua University and TUNA, including the site name, logo and related text and graphics, credit the project source on the home page, and release the modified source code following the GPLv2 licence. The project licence is GPL-2.0.

How do I build the mirror-web site locally?

Download the dynamic data files with wget into the static directory, initialise the submodules with git submodule update --init, install Ruby 3.0 or newer and Node.js 18 or newer, then run bundle install --deployment and npm install followed by bundle exec jekyll build, or bundle exec jekyll serve --livereload for a live preview. Setting JEKYLL_ENV to production minifies the build and VISUALIZER to true writes a bundle size analysis.

How does the front end get served by NGINX?

Through the fancy-index module with JavaScript injected via the njs module, which needs the fancyindex and js modules installed and the js module at version 0.7.9 or above. The server block sets a js_path, the time format and the header and footer fragments, and a location block imports the render function from the built njs file. A listing location then turns fancyindex on.

How are the help documents organised in mirror-web?

In two levels, with global documentation in a directory pulled in as a git submodule from the mirrorz documentation repository for content common to all mirrorz sites, and local documentation in this repository for site specific overrides. At build time the local files override the global ones, and each page directory holds a YAML configuration file plus Markdown content blocks, with new pages enabled through the enabled YAML file.

Official sources

  1. Issues
  2. License: GPL-2.0
  3. README
  4. tuna/mirror-web on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tuna-mirror-web.svg)](https://hysenlabs.com/projects/tuna-mirror-web)

Was this useful?