Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 1h ago 4b8c9e1d
history blame raw 8.3 KiB
1package instancerepo
2
3// Creating the repository Vole reads for its presentation. `vole admin init`
4// calls Ensure so a new instance starts with a repository an admin can open,
5// edit, and push to like any other. The Admin page calls the same function, so
6// an instance that predates this behaviour has the same repair path.
7
8import (
9 "errors"
10 "net/url"
11 "os"
12 "strings"
13
14 "rodent.software/vole/core/git"
15 "rodent.software/vole/forge/repo"
16)
17
18// DefaultDescription is what the repository list shows before an admin edits
19// it.
20const DefaultDescription = "Instance customization: profile README, custom CSS, favicon, and assets"
21
22// Ensure creates name when it is missing and seeds the starter tree when it has
23// no commits. An existing repository with history is left alone. owner may be
24// empty, which creates a private repository that instance admins can still
25// administer; author names the commit when the starter files are written.
26//
27// It reports whether it wrote anything, so a caller can audit a creation
28// without inventing its own answer to "was this new?".
29func Ensure(repos *repo.Store, name, owner, author string) (*repo.Meta, bool, error) {
30 name = strings.TrimSpace(name)
31 if name == "" || repos == nil {
32 return nil, false, nil
33 }
34 created := false
35 m, err := repos.Meta(name)
36 if err != nil {
37 if !errors.Is(err, os.ErrNotExist) {
38 return nil, false, err
39 }
40 m, err = repos.Create(name, owner, DefaultDescription)
41 if err != nil {
42 return nil, false, err
43 }
44 created = true
45 }
46 g := m.Git()
47 tip, err := g.RevParseRaw("refs/heads/" + m.DefaultBranch)
48 if err != nil {
49 return nil, created, err
50 }
51 if tip != "" {
52 return m, created, nil
53 }
54 tree, err := git.WriteFlatTree(g, Template(name))
55 if err != nil {
56 return nil, created, err
57 }
58 commit, err := g.CommitTreeAs(tree, "", "Create the instance repository", author)
59 if err != nil {
60 return nil, created, err
61 }
62 if err := g.UpdateRef("refs/heads/"+m.DefaultBranch, commit); err != nil {
63 return nil, created, err
64 }
65 return m, true, nil
66}
67
68// Template is the starter tree. The root README explains the fixed paths; the
69// profile files give an admin something to edit rather than a blank page, and
70// the CSS names the variables worth overriding. repoName is the repository the
71// starter tree is written to, so the introduction can link back to its overview.
72func Template(repoName string) map[string][]byte {
73 repoName = strings.TrimSpace(repoName)
74 // The page supplies the heading (instance_name), so the starter file carries
75 // no header of its own: it explains how to replace itself and links to the
76 // repository where the other files live.
77 intro := "This is the landing page introduction. Tell people what this forge is for."
78 if repoName != "" {
79 intro = "This is the landing page introduction. To customize it, edit `profile/README.md` in the [" +
80 repoName + "](/" + url.PathEscape(repoName) + ") repository; its README lists every file Vole reads."
81 }
82 return map[string][]byte{
83 "README.md": []byte(`# Instance customization
84
85This repository customizes the Vole instance. Vole creates it with ` + "`vole admin init`" + ` and
86reloads it on a short timer, so a push is live within a couple of seconds.
87
88Only these paths are read; other files are ignored, so this README and any
89notes can live here too.
90
91 profile/README.md the landing page introduction (markdown)
92 profile/private/README.md replaces it for signed-in viewers
93 profile/custom.css instance-wide styles, loaded after the theme
94 profile/themes/<id>.css one stylesheet per theme in Account -> Appearance
95 profile/favicon.svg replaces the built-in favicon
96 assets/ served at /-/static/custom/assets/
97
98Edit a file here and push, or open it in the web interface and use the editor.
99This repository has the usual pages, history, issues, and change requests. Its
100visibility is a repository setting, so an instance admin can publish it or keep
101it private.
102
103The files above are served to visitors (the private README only to signed-in
104viewers), even when the repository itself is private.
105
106## Themes
107
108The Account page's Appearance picker lists the built-in Everforest variants and
109every CSS file in profile/themes/. A theme is self-describing: its file name is
110the id and the label, and its rules are scoped to that id. Add one by creating
111the stylesheet:
112
113 profile/themes/acme-ocean.css
114 :root[data-theme="acme-ocean"] {
115 color-scheme: dark;
116 --bg: #0b1720;
117 --bg-panel: #102433;
118 --bg-raised: #163041;
119 --fg: #d7e8f0;
120 --fg-muted: #8fb0c0;
121 --fg-faint: #6f8f9f;
122 --accent: #38bdf8;
123 --accent-fg: #04121a;
124 --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
125 }
126
127 profile/themes/acme-paper.css
128 :root[data-theme="acme-paper"] {
129 color-scheme: light;
130 --bg: #fffdf7;
131 --bg-panel: #f8f3e8;
132 --bg-raised: #f1eadc;
133 --fg: #2b2a26;
134 --fg-muted: #6b675d;
135 --fg-faint: #8d887c;
136 --accent: #9a5b13;
137 --accent-fg: #fffdf7;
138 --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
139 }
140
141The file name becomes the id and the label: acme-ocean.css is the "Acme Ocean"
142theme. It must use lowercase letters, digits, and dashes, and cannot collide with
143a built-in value: system, nord, dracula, tokyo-night, or a name starting with
144everforest-, catppuccin-, solarized-, rose-pine-, kanagawa-, or ayu-. Each stylesheet is
145loaded after the built-in sheets and before profile/custom.css, and its rules
146apply only while that theme is selected. Set color-scheme and the tokens the
147theme changes: a light theme needs color-scheme: light and light surface and
148text tokens.
149
150profile/custom.css applies to every theme, so keep instance-wide overrides
151there and put a selectable palette in profile/themes/<id>.css. Keep each theme
152file small: every theme is linked on every page.
153
154The picker stores the choice in the browser, not on the account. A theme whose
155file is deleted falls back to System the next time the page loads.
156`),
157 "profile/README.md": []byte(intro + "\n"),
158 "profile/custom.css": []byte(`/* Custom styles. This file is loaded last: after vole.css, everforest.css, and
159 * any profile/themes/<id>.css, so it can override every theme.
160 *
161 * For a palette that appears in Account -> Appearance, add
162 * profile/themes/<id>.css; its file name is the id and label. This file is for
163 * overrides that apply no matter which theme is selected.
164 *
165 * Prefer tokens over rules: a token keeps the layout, contrast, and responsive
166 * behaviour that the built-in sheets already got right.
167 *
168 * Use :root[data-theme], not plain :root. Everforest sets its tokens on
169 * :root[data-theme^="everforest-"], which is more specific than :root, so a
170 * plain :root override can lose even though this file loads later.
171 */
172
173:root[data-theme] {
174 /* Uncomment one line at a time and reload the page.
175 *
176 * --accent: #c678dd;
177 * --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
178 * --radius: 4px;
179 * --radius-lg: 6px;
180 * --page-max: 1200px;
181 * --font: ui-sans-serif, system-ui, sans-serif;
182 */
183}
184
185/* A value that has to differ between dark and light modes belongs in one of
186 * these blocks. They match every Everforest variant.
187 *
188 * :root[data-theme^="everforest-dark"] { --bg: #101418; }
189 * :root[data-theme^="everforest-light"] { --bg: #fffdf7; }
190 */
191
192/* Tokens, grouped by what they change:
193 *
194 * surfaces --bg --bg-panel --bg-raised --bg-hover --bg-active
195 * --border --border-strong
196 * text --fg --fg-muted --fg-faint
197 * identity --accent --accent-fg --accent-soft
198 * status --good --good-soft --bad --bad-soft --warn --warn-soft
199 * --running --merged --merged-soft --status-fg
200 * shape --radius --radius-lg --page-max --page-gutter --search-w
201 * type --font --mono --fs --lh
202 * (--font-scale is the reader's text-size preset: leave it alone)
203 * syntax --syn-comment --syn-punctuation --syn-operator --syn-number
204 * --syn-string --syn-keyword --syn-function --syn-type
205 * --syn-variable --syn-added --syn-deleted
206 */
207
208/* If a token cannot express the change, these page-level classes cover most
209 * re-skins. They are not a public API; an upgrade may move markup, so prefer
210 * tokens when one exists.
211 *
212 * .panel .readme .md .repo-card .repo-grid .home-grid .home-rail
213 * .repohead .tabs .chip .label .status .config-table .diff .blob
214 */
215`),
216 }
217}
218