vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Customize the instance from an instance repository
6ce635a84ad5d5d0c862d4deb4d46489dc97c677 · Keenan Nemetz · 1d ago · parent f2fd4024
Customize the instance from an instance repository
The landing page's prose was config-only, so changing it meant editing
vole.toml on the host and restarting, and the packaged service cannot write
that file. Custom CSS, assets, and a favicon had nowhere to live at all.
This adds an instance repository, modelled on GitHub's .github repository:
fixed paths in one private repository, read as data rather than executed.
profile/README.md the landing page introduction
profile/private/README.md replaces it for signed-in viewers
profile/custom.css loaded after the built-in stylesheets
profile/favicon.svg favicon override
assets/ served at /static/custom/assets/
forge/instancerepo reads one commit into an immutable snapshot on a two
second timer and swaps it atomically; a request never runs git, and a load
failure keeps the previous theme. Markdown goes through the issue sanitiser,
assets are an extension whitelist with size and count caps, and scripts and
HTML are not assets. The repository is private to instance admins and hidden
from the home grid and activity rail, but its files are served to visitors.
The Admin Configuration tab grows an Instance repository panel: status, the
last commit, the last load error, the clone URL, the recognized paths, and a
button that creates the repository with starter files. The home page's pencil
keeps the in-place editor, but a save is now a commit to profile/README.md
with the admin as the author and web.home.intro in the audit log, instead of a
row in users.db.
The rest of the landing page stays in the shape it had: instance_name as the
heading, the introduction card, then the repository grid beside the Latest
rail. Moving a configurable page layout into a template system was tried and
dropped: this is fixed files, not a templating surface.
This replaces the pending introduction-store change, which is closed.
docs/configuration.md +42−10
| @@ -4,9 +4,15 @@ Vole is designed to work out of the box. The defaults are enough for a local | |||
| 4 | 4 | instance. Change them when you want Vole to listen on another address, limit what | |
| 5 | 5 | people can push, or serve websites. | |
| 6 | 6 | ||
| 7 | There is no control panel. One TOML file is the whole configuration. An | ||
| 7 | There is no general control panel. One TOML file is the whole configuration. An | ||
| 8 | 8 | instance admin can see the effective values and the file they came from on the | |
| 9 | Admin page's Configuration tab, but that page is read-only. | ||
| 9 | Admin page's Configuration tab, and that view is read-only. | ||
| 10 | |||
| 11 | The one exception is the customizable repository: `instance_repo` names a | ||
| 12 | private repository whose tree supplies the landing page's introduction, a | ||
| 13 | stylesheet, a favicon, and static assets. It is not written to `vole.toml`, | ||
| 14 | because the packaged service runs with `ProtectSystem=strict` and cannot write | ||
| 15 | its config file. See [Customizing the instance](#customizing-the-instance). | ||
| 10 | 16 | ||
| 11 | 17 | ## A minimal config | |
| 12 | 18 | ||
| @@ -50,9 +56,9 @@ vole admin account list | |||
| 50 | 56 | | `ssh_max_connections` | `64` | Simultaneous connections on that listener; past it a connection is closed before the handshake rather than queued | | |
| 51 | 57 | | `ssh_max_git_processes` | `16` | Git processes across every SSH connection; past it a command waits a short while, then is told to retry | | |
| 52 | 58 | | `ssh_handshake_timeout_seconds` | `20` | How long a connection may take to finish the SSH handshake before it is dropped | | |
| 59 | | `instance_name` | `"Vole"` | Heading on the home page | | ||
| 60 | | `instance_repo` | `"vole-instance"` | Repository whose tree customizes the instance; empty disables it | | ||
| 53 | 61 | | `public_url` | empty | Canonical browser address; must be HTTPS to enable passkey sign-in | | |
| 54 | | `instance_name` | `"Vole"` | Name the landing page shows for this forge | | ||
| 55 | | `instance_tagline` | `"A forge that lives in your Git repo."` | One line under the name on the landing page | | ||
| 56 | 62 | | `acme_email` | empty | Contact address given to the certificate authority | | |
| 57 | 63 | | `acme_domains` | empty | Host names to obtain certificates for; defaults to the host in `public_url` | | |
| 58 | 64 | | `acme_cache_dir` | `<data_dir>/acme` | Where the ACME account key and certificates are stored | | |
| @@ -126,18 +132,44 @@ go with it; `git lfs` will report a missing object rather than fetch from SSH. | |||
| 126 | 132 | A release archive is served under the same limit: `git_http_timeout_seconds` | |
| 127 | 133 | does not apply to the release page's tarball, which has its own concurrency bound. | |
| 128 | 134 | ||
| 129 | ## Instance name and tagline | ||
| 135 | ## Customizing the instance | ||
| 136 | |||
| 137 | `instance_repo` names a repository whose tree customizes the instance. The idea is | ||
| 138 | GitHub's `.github` repository: fixed paths read as data, not templates. | ||
| 130 | 139 | ||
| 131 | The landing page names the forge above its repository list. Set these when the | ||
| 132 | instance belongs to a team, so a visitor knows where they are without reading | ||
| 133 | the URL: | ||
| 140 | ```toml | ||
| 141 | instance_repo = "vole-instance" # the default; empty disables customization | ||
| 142 | ``` | ||
| 143 | |||
| 144 | An instance admin can scaffold it from **Admin → Configuration** (the Instance | ||
| 145 | repository panel), or create a repository with that name normally and push the | ||
| 146 | files. The repository is private to instance admins and hidden from the home page | ||
| 147 | and activity feed; the files below are served to visitors. | ||
| 148 | |||
| 149 | | Path | What it does | | ||
| 150 | |---|---| | ||
| 151 | | `profile/README.md` | Landing page introduction; markdown, sanitized like an issue body | | ||
| 152 | | `profile/private/README.md` | Replaces it for signed-in viewers when present | | ||
| 153 | | `profile/custom.css` | Loaded after the built-in stylesheets | | ||
| 154 | | `profile/favicon.svg` | Replaces the favicon links | | ||
| 155 | | `assets/` | Served at `/static/custom/assets/`; CSS, images and fonts only | | ||
| 156 | |||
| 157 | Other files are ignored, and assets are limited to CSS, images and fonts: a | ||
| 158 | same-origin script is not a theme. The pencil on the home page commits | ||
| 159 | `profile/README.md` with the admin as the author and records the change as | ||
| 160 | `web.home.intro`; the private README and everything else are edited by pushing. | ||
| 161 | A push is live within a couple of seconds, and a load failure keeps the previous | ||
| 162 | theme. | ||
| 163 | |||
| 164 | The introduction is rendered and sanitized the same way an issue body is, and it | ||
| 165 | appears above the repository list. An empty `profile/README.md` removes the | ||
| 166 | card's body. The heading is `instance_name`, which is what the forge calls itself: | ||
| 134 | 167 | ||
| 135 | 168 | ```toml | |
| 136 | 169 | instance_name = "Acme Forge" | |
| 137 | instance_tagline = "Code, issues, change requests, and CI for the Acme team." | ||
| 138 | 170 | ``` | |
| 139 | 171 | ||
| 140 | Neither setting changes the `vole` wordmark in the top bar. | ||
| 172 | None of this changes the `vole` wordmark in the top bar. | ||
| 141 | 173 | ||
| 142 | 174 | ## Public URL | |
| 143 | 175 | ||