vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
| 1 | # Sites |
| 2 | |
| 3 | A website can be a branch. That keeps publishing simple and keeps the files with |
| 4 | the repository. Vole serves a repository's `pages` branch at `<repo>.<pages_domain>`. |
| 5 | There is no build step on the server: change the branch and the next request sees |
| 6 | the new files. |
| 7 | |
| 8 | ## Publish a build directory |
| 9 | |
| 10 | Most websites are generated into a directory such as `dist/`. Vole can publish |
| 11 | that directory as a snapshot, so the branch contains exactly those files. If you |
| 12 | delete a page from the build, it stops being served. |
| 13 | |
| 14 | ```sh |
| 15 | npm run build |
| 16 | vole pages deploy dist # publish dist/ to the pages branch |
| 17 | vole pages deploy dist --dry-run # show changes without pushing |
| 18 | vole pages status # show the current branch and files |
| 19 | vole pages unpublish # take the site down |
| 20 | ``` |
| 21 | |
| 22 | Publishing the same files twice does nothing. Use `--branch` if the instance |
| 23 | serves a different branch than `pages`. |
| 24 | |
| 25 | Dotfiles such as `.nojekyll` and `.well-known/` are published. Vole skips `.git` |
| 26 | and symbolic links. A site is just files, and a link pointing outside the build |
| 27 | directory would publish something you did not choose. |
| 28 | |
| 29 | ## Publish by hand |
| 30 | |
| 31 | Plain Git is enough: |
| 32 | |
| 33 | ```sh |
| 34 | git switch --orphan pages |
| 35 | printf '<h1>hello</h1>\n' > index.html |
| 36 | git add index.html |
| 37 | git commit -m "site" |
| 38 | git push origin pages # live at https://demo.pages.example.com/ |
| 39 | ``` |
| 40 | |
| 41 | ## Turn sites on |
| 42 | |
| 43 | ```toml |
| 44 | pages_domain = "pages.example.com" # <repo>.pages.example.com |
| 45 | pages_ref = "pages" # branch to serve |
| 46 | pages_procs = 4 # Git reader processes per active site |
| 47 | pages_cache_bytes = 33554432 # 32 MiB of cached files |
| 48 | ``` |
| 49 | |
| 50 | Set `pages_domain` to enable websites. If you change `pages_ref`, pass the same |
| 51 | branch name to `vole pages deploy --branch`. |
| 52 | |
| 53 | ## How sites are served |
| 54 | |
| 55 | - A directory serves its `index.html`. A directory URL without a trailing slash |
| 56 | redirects to the version with one. |
| 57 | - Vole uses the file's Git hash as its `ETag` and the commit time as |
| 58 | `Last-Modified`, so unchanged pages return `304`. |
| 59 | - Vole checks the branch and repository visibility once a second, so changes take |
| 60 | effect within that window. The repository header links to the site only once |
| 61 | the pages branch exists and the repository is public; until then there is no |
| 62 | Site button, because the link would lead to a 404. |
| 63 | - A private repository has no public website. There is no logged-in reader, so the |
| 64 | site returns `404`, just like its clone URL. |
| 65 | |
| 66 | With automatic HTTPS enabled (`https_addr`), Vole obtains a certificate for each |
| 67 | site host the first time it is requested, as long as the repository exists and is |
| 68 | publicly readable. A wildcard DNS record pointing at the instance is enough; |
| 69 | wildcard certificates are not supported, so each host is issued separately. Without `https_addr`, terminate |
| 70 | TLS in a proxy in front of `http_addr` and keep the `Host` header: it is how Vole |
| 71 | knows which site was requested. See |
| 72 | [Configuration § Automatic HTTPS with ACME](configuration.md#automatic-https-with-acme) |
| 73 | and [Configuration § Running behind a proxy](configuration.md#running-behind-a-proxy). |
| 74 | |
| 75 | A workflow can publish the site as its last step. See |
| 76 | [Workflows § Publish a website](workflows.md#publish-a-website). |
| 77 | |
| 78 | ## Same-site risk |
| 79 | |
| 80 | A site is content somebody else wrote, served from a hostname you control. If |
| 81 | `pages_domain` shares a registrable domain with the forge — `forge.example.com` |
| 82 | and `*.pages.example.com`, say — then every site is *same-site* with the forge, |
| 83 | and a malicious site can: |
| 84 | |
| 85 | - make the browser send the forge's `SameSite=Lax` cookies on a top-level |
| 86 | navigation (Vole's writes still need a CSRF token, so this does not authorize |
| 87 | anything on its own, but it is the assumption a weaker application would rely on); |
| 88 | - open the forge in a window or frame and interact with it as a same-site |
| 89 | document, which is why `Cross-Origin-Opener-Policy` and the CSP frame rules are |
| 90 | set on every page; |
| 91 | - share a cookie domain if you ever set one wider than a host. |
| 92 | |
| 93 | Use a separate registrable domain for sites when the repositories publishing them |
| 94 | are not all trusted: `pages.example.net` for a forge at `forge.example.com`. The |
| 95 | two then cannot be same-site. If the sites are as trusted as the forge itself, a |
| 96 | subdomain is fine and keeps certificate management to one domain. |
| 97 |