Skip to content

Commit d3c8c74

Browse files
committed
Introduce community mirrors
The 'Download' page has a link to the new 'Community Mirrors' page which describes how to use them. The mirror list is accessible at '/download/community-mirrors.txt', which is generated from the Ziggy source in 'assets/community-mirrors.ziggy'. I have confirmed with the owners of these Zig mirrors that they are happy for their mirrors to be used in this context.
1 parent ca30d0e commit d3c8c74

File tree

9 files changed

+317
-4
lines changed

9 files changed

+317
-4
lines changed

MIRRORS.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
> [!NOTE]
2+
> Are you trying to download Zig from a mirror? If so, you're in the wrong place! This documentation is for anyone who wants to *host* a community mirror.
3+
> For instructions on *using* the mirrors, check out the [Community Mirrors](https://ziglang.org/download/community-mirrors/) page on the Zig website.
4+
5+
# Community Mirrors
6+
7+
The Zig mirrors in this repository are all community-maintained and unaffiliated with the ZSF. Anyone is welcome to host their own mirror and add it to the
8+
list. Because Zig's tarballs are cryptographically signed, no mirror need be trusted. Of course, if a mirror is found to be malicious, it will be removed from
9+
the list.
10+
11+
The following rules define how a mirror works and the requirements it is expected to fulfil.
12+
13+
* A mirror provides a single base URL, which we will call `X`.
14+
* `X` **may** include a path component, but is not required to. For instance, `https://foo.bar/zig/` is okay,
15+
as is `https://zig.baz.qux/`.
16+
* The mirror **must** support HTTPS with a valid signed certificate. `X` **must** start with `https://`.
17+
* The mirror **must** cache tarballs locally. For instance, it may not simply forward all requests to another mirror.
18+
* The mirror **may** routinely evict its local tarball caches based on any reasonable factor, such as age, access frequency, or the existence of newer versions. This does not affect whether the mirror may respond with 404 Not Found for requests to these files (see below).
19+
* The mirror **must** download tarballs from `https://ziglang.org/` or a valid mirror of it.
20+
* Typically, it is best to download only from `https://ziglang.org/`. One possible exception is when a pre-release is requested which is more likely to be available from an alternative mirror.
21+
* When a mirror receives a GET request for `X/<filename>`, the behavior depends on `<filename>`.
22+
* Parse the file name to extract the Zig version string. More details on this are [below](#parsing-versions).
23+
* Determine whether this is a "normal" or "pre-release" [Semantic Version](https://semver.org/).
24+
* If this is a "normal" version, respond with 200 OK and the file found at `https://ziglang.org/download/<version>/<filename>`.
25+
* If the release version is "0.5.0" or older (according to Semantic Versioning), the mirror **may** respond with 404 Not Found, but is not required to.
26+
* Otherwise, the mirror **must** return the tarball.
27+
* These requirements apply equally to source tarballs (e.g. `zig-0.14.1.tar.xz`), bootstrap source tarballs (e.g. `zig-bootstrap-0.14.1.tar.xz`), and binary tarballs (e.g. `zig-x86_64-linux-0.14.1.tar.xz`).
28+
* Otherwise (i.e. if this is a "pre-release" version), respond with 200 OK and the file found at `https://ziglang.org/builds/<filename>`.
29+
* If the version is older than the latest "release" version (according to Semantic Versioning), the mirror **may** respond with 404 Not Found, but is not required to.
30+
* Otherwise, the mirror **must** return the tarball.
31+
* These requirements apply equally to source tarballs (e.g. `zig-0.15.0-dev.671+c907866d5.tar.xz`), bootstrap source tarballs (e.g. `zig-bootstrap-0.15.0-dev.671+c907866d5.tar.xz`), and binary tarballs (e.g. `zig-x86_64-linux-0.15.0-dev.671+c907866d5.tar.xz`).
32+
* Invalid accesses, for instance to malformed filenames, **may** cause a 404 Not Found response.
33+
* Accesses to file names which do not end with ".zip", ".tar.xz", ".zip.minisig", or ".tar.xz.minisig", **may** cause a 404 Not Found response.
34+
* Files provided by the mirror **must** be bit-for-bit identical to their `https://ziglang.org/` counterparts.
35+
* If a mirror is required to serve a tarball which is has not yet cached locally, it **must** immediately download it from its source at `https://ziglang.org`, and respond with that downloaded file.
36+
* The mirror **may** rate-limit accesses. If an access failed due to rate-limiting, the mirror **should** respond with 429 Too Many Requests.
37+
* The mirror **may** undergo maintenance, upgrades, and other scheduled downtime. If an access fails for this reason, where possible, the mirror **should** respond with 503 Unavailable. The mirror **should** try to minimize such downtime.
38+
* The mirror **may** undergo occasional unintended and unscheduled downtime. The mirror **must** go to all efforts to minimize such outages, and **must** resolve such outages within a reasonable time upon being notified of them.
39+
* The mirror **may** observe the query parameter named `source` to learn about the origin of its traffic.
40+
* Clients are encouraged, but not required, to provide this query parameter to indicate what service triggered the request. For instance, the `mlugg/setup-zig` GitHub Action passes this query parameter as `?source=github-mlugg-setup-zig`.
41+
42+
## Parsing Versions
43+
44+
Mirrors are required to parse tarball file names to extract the Zig version from them. Unfortunately,
45+
this is complicated a little by two factors: the existence of "source" tarballs, and the fact that
46+
Zig's tarball naming schema changed with the 0.14.1 release.
47+
48+
If you have access to Regular Expressions, a simple strategy is to search the tarball name for
49+
`/\d+\.\d+\.\d+(-dev\.\d+\+[0-9a-f]+)?/`. This is probably inefficient, but that is unlikely to
50+
matter in practice.
51+
52+
An alternative strategy which is simpler but requires slightly more code is as follows:
53+
* Strip a supported extension (`.zip`, `.tar.xz`, `.zip.minisig`, `.tar.xz.minisig`) from the end of the file name.
54+
* Find the last occurrence of "-" in the file name. If that byte is followed by the string "dev", find the previous occurence of "-" instead.
55+
* The string after that "-" byte is the Zig version.
56+
57+
It is strongly discouraged for checks to rely on specific architecture or OS names, since the set of targets for which tarballs are provided could be extended at any time.
58+
59+
## Adding A Mirror
60+
61+
Once a mirror complies with the requirements listed above, you can add it to the list of mirrors on ziglang.org by modifying the list in
62+
`assets/community-mirrors.ziggy` and opening a pull request with your changes. The diff should just add a single entry to the end of the list:
63+
64+
```diff
65+
[
66+
{
67+
.url = "https://a.com",
68+
.username = "a",
69+
.email = "a@a.com",
70+
},
71+
{
72+
.url = "https://b.com/zig",
73+
.username = "b",
74+
.email = "b@b.com",
75+
},
76+
+ {
77+
+ .url = "https://mymirror.net",
78+
+ .username = "my-github-username",
79+
+ .email = "my@email.com",
80+
+ },
81+
]
82+
```
83+
84+
Note that the Zig core team always reserves the right to exclude mirrors from this list for any (or no) reason.
85+
86+
## Community-Written Solutions
87+
88+
Some members of the Zig community have written software for hosting Zig mirrors, making it easier to comply with the requirements listed above. If you have an
89+
open-source repository which would fit here, feel free to open a pull request adding it below.
90+
91+
> [!WARNING]
92+
> The Zig Software Foundation has not written or audited this code. As such, no guarantees can be made regarding its correctness, and it should **not** be
93+
> implicitly trusted. You are strongly encouraged to audit this software before using it, particularly if it will not be run in an isolated environment.
94+
95+
* [hexops/wrench](https://github.com/hexops/wrench?tab=readme-ov-file#run-your-own-ziglangorgdownload-mirror)

assets/community-mirrors.ziggy

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
.url = "https://pkg.machengine.org/zig",
4+
.username = "emidoots",
5+
.email = "emi@hexops.com",
6+
},
7+
{
8+
.url = "https://zigmirror.hryx.net/zig",
9+
.username = "hryx",
10+
.email = "codroid@gmail.com",
11+
},
12+
{
13+
.url = "https://zig.linus.dev/zig",
14+
.username = "linusg",
15+
.email = "mail@linusgroh.de",
16+
},
17+
]

assets/css/style.css

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ blockquote {
467467
}
468468

469469

470-
code.zig {
470+
code {
471471
--light-yellow: #e5c07b;
472472
--dark-yellow: #d19a66;
473473
--blue: #61afef;
@@ -517,3 +517,49 @@ video {
517517
margin-top: 1em;
518518
display: inline-block;
519519
}
520+
521+
code.python {
522+
color: var(--cyan);
523+
}
524+
525+
code.python .punctuation {
526+
color: var(--cyan);
527+
}
528+
529+
code.python .string {
530+
color: var(--dark-yellow);
531+
}
532+
533+
code.python .variable,
534+
code.python .parameter,
535+
code.python .exception,
536+
code.python .field {
537+
color: var(--light-yellow);
538+
}
539+
540+
code.python .keyword.function {
541+
color: var(--light-red);
542+
}
543+
544+
code.python .bracket {
545+
color: var(--cyan);
546+
}
547+
548+
code.python .function {
549+
color: var(--blue);
550+
}
551+
552+
code.python .builtin {
553+
color: var(--magenta);
554+
}
555+
556+
code.python .number {
557+
color: var(--magenta);
558+
}
559+
560+
code.python .operator,
561+
code.python .qualifier,
562+
code.python .keyword,
563+
code.python .attribute {
564+
color: var(--light-red);
565+
}

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
.hash = "doctest-0.1.0-lY-6k0W2AQB2cw2vD3306oCziQrVnG013naZd8hMkuAQ",
1414
},
1515
.zine = .{
16-
.url = "git+https://github.com/kristoff-it/zine#5408d3dff6107fc449a6d24d90f379b6fffdb4e6",
17-
.hash = "zine-0.9.0-ou6nIBcqFgCkkL8OQnHRzEKboVcAvIp76bnf6n9qeBvU",
16+
.url = "git+https://github.com/kristoff-it/zine#ff0b7b1222fe6529fc36e876a71a99379ad761c4",
17+
.hash = "zine-0.9.0-ou6nIEJTFgDwdlQnB6NtTnZneWKgg-qoVPaXyU2PjWF2",
1818
},
1919
},
2020
}

content/en-US/download.smd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ Files are signed with [minisign](https://jedisct1.github.io/minisign/) using thi
1818
```
1919
RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
2020
```
21+
22+
> # [Setting up an automation?]($block)
23+
> If you're automating the process of downloading Zig, you might want to learn about [Community Mirrors](/download/community-mirrors) to avoid downtime and help us save on bandwidth costs.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
.title = "Community Mirrors",
3+
.author = "",
4+
.date = @date("2024-08-07:00:00:00"),
5+
.layout = "download/community-mirrors.shtml",
6+
.alternatives = [{
7+
.name = "list",
8+
.layout = "download/community-mirrors-list.shtml",
9+
.output = "/download/community-mirrors.txt",
10+
}],
11+
.custom = { "mobile_menu_title": "Mirrors" },
12+
---
13+
14+
If you're setting something up which will automatically download Zig, like CI, you might be interested in using community mirrors instead of downloading from
15+
ziglang.org.
16+
17+
The ziglang.org website does not offer any uptime or speed guarantees, meaning that your CI will sporadically fail or have slower runs if it hardcodes it as a
18+
download URL. In fact, configuring your CI to fetch from ziglang.org directly contributes to uptime and speed issues, because this site is [intentionally hosted
19+
on a simple one-computer configuration](/news/migrate-to-self-hosting). Instead, it is often a good idea to fetch Zig from one of many community-maintained
20+
mirrors. These mirrors are not officially endorsed by the Zig Software Foundation, but they can be used without security risks thanks to our signing of
21+
archives. While no individual mirror has an uptime or speed guarantee, configuring your automation to cycle through the list of available mirrors can
22+
effectively guarantee high uptime in practice.
23+
24+
> # [Security Notice]($block)
25+
> Community mirrors are not officially trusted or endorsed by the Zig Software Foundation, and could in theory serve malicious binaries. If you are using them,
26+
> you **must** make sure to validate the minisign signature for every tarball you download against the ZSF's public key, available on [the download
27+
> page](/download).
28+
29+
## GitHub Actions
30+
31+
If you are setting up an automation using GitHub Actions, you may be interested in the
32+
[mlugg/setup-zig](https://github.com/marketplace/actions/setup-zig-compiler) Action (note that this is not an official ZSF project). Not only does it install a
33+
Zig version of your choice from a community mirror, but it also saves your Zig cache directory between workflow runs, allowing for faster rebuilds.
34+
35+
## Using Mirrors
36+
37+
The list of community mirrors is available in a newline-separated ASCII text file at https://ziglang.org/download/community-mirrors.txt. Tooling is recommended
38+
to fetch this list and try mirrors in a randomized order (to avoid putting excessive load on any one mirror, as this slows it down for everyone).
39+
40+
Every Zig tarball is associated with a [minisign](https://jedisct1.github.io/minisign/) signature file, which can also be downloaded from mirrors. **When you
41+
download a tarball from a mirror, you must also download its associated signature and verify the tarball against it.** Failing to check the signature could
42+
theoretically leave you vulnerable to malicious mirrors hosting modified tarballs.
43+
44+
Put simply, the recommended strategy is approximately this pseudocode:
45+
46+
```python
47+
pubkey = "(copy this from https://ziglang.org/download)"
48+
tarball_name = "zig-x86_64-linux-0.14.1.tar.xz"
49+
# To improve uptime, optionally cache this GET:
50+
mirrors = http_get("https://ziglang.org/download/community-mirrors.txt")
51+
# ASCII-encoded, one mirror per line, newlines are LF, there is a trailing newline.
52+
shuffled = shuffle_lines(mirrors)
53+
for mirror_url in shuffled:
54+
tarball = http_get(f"{mirror_url}/{tarball_name}?source=my_automation_name")
55+
if success:
56+
# NEVER SKIP THIS STEP. The signature must be verified before the tarball is deemed safe.
57+
signature = http_get(f"{mirror_url}/{tarball_name}.minisig?source=my_automation_name")
58+
if success and minisign_verify(tarball, signature, pubkey):
59+
print("Successfully fetched Zig 0.14.1!")
60+
```
61+
62+
Because ziglang.org does not have guaranteed uptime, the `community-mirrors.txt` file may at times become inaccessible. For this reason, you may wish to
63+
consider caching its contents to prevent disruption in the event that ziglang.org encounters downtime. The recommended refetch interval is approximately
64+
once per day. At this point in time, mirrors may be added or removed on a monthly basis as the ecosystem evolves, so periodic re-fetching is essential.
65+
66+
Written more precisely, here is the key information and recommend workflow for downloading Zig tarballs:
67+
68+
* The mirror list file is available at https://ziglang.org/download/community-mirrors.txt.
69+
* Because ziglang.org does not guarantee uptime, it may be desirable to cache this file.
70+
* The mirror list file contains ASCII-encoded mirror URLs, separated with newline characters (ASCII LF 0x20). There is a trailing newline. There is no other whitespace. There are no blank lines.
71+
* Mirrors are required to support HTTPS. Every line in the mirror list file begins with "https://".
72+
* Mirrors cannot guarantee uptime, so if one fails to serve you a tarball, you should try another. Ideally, shuffle the list, and try each mirror in turn.
73+
* Usually, the first one will work. If no mirror works, you may choose to try `ziglang.org` as a final fallback.
74+
* To download a tarball from a mirror, perform a GET request to "mirror/filename", where "mirror" is the mirror URL, and "filename" is the basename of the corresponding tarball on ziglang.org (e.g. `zig-x86_64-linux-0.14.1.tar.xz`).
75+
* You are highly encouraged to include in your request a query parameter named `source` containing a string indicating what is making this request. For instance, the `mlugg/setup-zig` GitHub Action passes it as `?source=github-mlugg-setup-zig`.
76+
* Source tarballs, bootstrap tarballs, and binary tarballs are available from all listed mirrors, as well as minisign signatures for all such files.
77+
* Binary tarballs for recent Zig versions are of the form `zig-x86_64-linux-0.14.1.tar.xz`.
78+
* If a mirror responds with a HTTP status code other than 200 OK:
79+
* `503 Unavilable` may indicate scheduled downtime.
80+
* `429 Too Many Requests` may indicate intentional rate-limiting.
81+
* `404 Not Found` is a permitted response when requesting Zig releses 0.5.0 or earlier, or Zig development builds earlier than the current latest release.
82+
* Otherwise, feel free to [open an issue](https://github.com/ziglang/www.ziglang.org/issues/new) to inform us of the problem.
83+
* The Zig Software Foundation can never guarantee the security of any mirror, so every time a tarball is downloaded, it is **essential** to also download the minisign signature (suffix the filename with ".minisig") and verify it against the ZSF's public key (which you should copy from the ziglang.org/download page). **Never skip this step.**
84+
* If a mirror responds with `200 OK` but signature validation fails on the returned tarball, feel free to [open an
85+
issue](https://github.com/ziglang/www.ziglang.org/issues/new) to inform us of the problem.
86+
87+
## Hosting a Mirror
88+
89+
If you are interested in hosting a mirror, please consult the [documentation in the www.ziglang.org
90+
repository](https://github.com/ziglang/www.ziglang.org/blob/main/MIRRORS.md). Thank you for helping
91+
to improve and decentralize the Zig ecosystem!

layouts/download.shtml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@
66
rel="stylesheet"
77
href="$site.asset('css/home.css').link()"
88
>
9+
<style>
10+
.block {
11+
border: 2px solid grey;
12+
}
13+
.block h1 {
14+
margin: 0;
15+
font-size: 1.1em;
16+
background: #f7a41d;
17+
padding: 0.4em 0.7em;
18+
color: black;
19+
}
20+
.block p {
21+
margin: 0;
22+
padding: 0.5em 0 0.5em 1.0em;
23+
}
24+
</style>
925
</head>
1026
<div id="content">
1127
<div class="container">
@@ -138,4 +154,4 @@
138154
</div>
139155
</ctx>
140156
</div>
141-
</div>
157+
</div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<ctx :loop="$site.asset('community-mirrors.ziggy').ziggy()"><ctx :text="$loop.it.get('url')"></ctx>
2+
</ctx><ctx :if="$page.draft">
3+
This file intentionally does not have a trailing newline in order to prevent a double-newline after
4+
the final mirror. If you modify this file, carefully inspect `community-mirrors.txt` to check you
5+
haven't made an unintentional change to the output.
6+
</ctx>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<extend template="locale.shtml">
2+
<title id="title" :text="$page.title"></title>
3+
<head id="head">
4+
<link
5+
type="text/css"
6+
rel="stylesheet"
7+
href="$site.asset('css/home.css').link()"
8+
>
9+
<style>
10+
.block {
11+
border: 2px solid grey;
12+
}
13+
.block h1 {
14+
margin: 0;
15+
font-size: 1.1em;
16+
background: #f7a41d;
17+
padding: 0.4em 0.7em;
18+
color: black;
19+
}
20+
.block p {
21+
margin: 0;
22+
padding: 0.5em 0 0.5em 1.0em;
23+
}
24+
</style>
25+
</head>
26+
<div id="content">
27+
<div class="container">
28+
<ctx back="$page.parentSection()">
29+
<a id="back" href="$ctx.back.link()">
30+
<ctx :text="$i18n.get('back')"></ctx>
31+
<b :text="$ctx.back.title"></b>
32+
</a>
33+
</ctx>
34+
<h1 class="title" :text="$page.title"></h1>
35+
<ctx :if="$page.custom.get?('toc')" :html="$page.toc()">
36+
</ctx>
37+
<ctx :html="$page.content()"></ctx>
38+
</div>
39+
</div>

0 commit comments

Comments
 (0)