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.
Create the instance repository on init and list it normally
3e7c5e6797105f87fe593bb4c34743d3615ab598 · Keenan Nemetz · 1d ago · parent 6ce635a8
Create the instance repository on init and list it normally `vole admin init` now creates the configured instance repository with the starter tree and indexes it, so a new instance starts with it. The repository is no longer hidden from the home page or activity feed: it has the same pages, history, and settings as any other, and its visibility is its own. The Admin page's create button remains as the repair path for an instance that predates this, using the same starter tree from the instancerepo package.
forge/instancerepo/seed.go +111
| @@ -0,0 +1,111 @@ | |||
| 1 | package 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 | |||
| 8 | import ( | ||
| 9 | "errors" | ||
| 10 | "os" | ||
| 11 | "strings" | ||
| 12 | |||
| 13 | "github.com/rodent-software/vole/core/git" | ||
| 14 | "github.com/rodent-software/vole/forge/repo" | ||
| 15 | ) | ||
| 16 | |||
| 17 | // DefaultDescription is what the repository list shows before an admin edits | ||
| 18 | // it. | ||
| 19 | const DefaultDescription = "Instance customization: profile README, custom CSS, favicon, and assets" | ||
| 20 | |||
| 21 | // Ensure creates name when it is missing and seeds the starter tree when it has | ||
| 22 | // no commits. An existing repository with history is left alone. owner may be | ||
| 23 | // empty, which creates a private repository that instance admins can still | ||
| 24 | // administer; author names the commit when the starter files are written. | ||
| 25 | // | ||
| 26 | // It reports whether it wrote anything, so a caller can audit a creation | ||
| 27 | // without inventing its own answer to "was this new?". | ||
| 28 | func Ensure(repos *repo.Store, name, owner, author, instanceName string) (*repo.Meta, bool, error) { | ||
| 29 | name = strings.TrimSpace(name) | ||
| 30 | if name == "" || repos == nil { | ||
| 31 | return nil, false, nil | ||
| 32 | } | ||
| 33 | created := false | ||
| 34 | m, err := repos.Meta(name) | ||
| 35 | if err != nil { | ||
| 36 | if !errors.Is(err, os.ErrNotExist) { | ||
| 37 | return nil, false, err | ||
| 38 | } | ||
| 39 | m, err = repos.Create(name, owner, DefaultDescription) | ||
| 40 | if err != nil { | ||
| 41 | return nil, false, err | ||
| 42 | } | ||
| 43 | created = true | ||
| 44 | } | ||
| 45 | g := m.Git() | ||
| 46 | tip, err := g.RevParseRaw("refs/heads/" + m.DefaultBranch) | ||
| 47 | if err != nil { | ||
| 48 | return nil, created, err | ||
| 49 | } | ||
| 50 | if tip != "" { | ||
| 51 | return m, created, nil | ||
| 52 | } | ||
| 53 | tree, err := git.WriteFlatTree(g, Template(instanceName)) | ||
| 54 | if err != nil { | ||
| 55 | return nil, created, err | ||
| 56 | } | ||
| 57 | commit, err := g.CommitTreeAs(tree, "", "Create the instance repository", author) | ||
| 58 | if err != nil { | ||
| 59 | return nil, created, err | ||
| 60 | } | ||
| 61 | if err := g.UpdateRef("refs/heads/"+m.DefaultBranch, commit); err != nil { | ||
| 62 | return nil, created, err | ||
| 63 | } | ||
| 64 | return m, true, nil | ||
| 65 | } | ||
| 66 | |||
| 67 | // Template is the starter tree. The root README explains the fixed paths; the | ||
| 68 | // profile files give an admin something to edit rather than a blank page, and | ||
| 69 | // the CSS names the variables worth overriding. | ||
| 70 | func Template(instanceName string) map[string][]byte { | ||
| 71 | name := strings.TrimSpace(instanceName) | ||
| 72 | if name == "" { | ||
| 73 | name = "Vole" | ||
| 74 | } | ||
| 75 | return map[string][]byte{ | ||
| 76 | "README.md": []byte(`# Instance customization | ||
| 77 | |||
| 78 | This repository customizes the Vole instance. Vole creates it with ` + "`vole admin init`" + ` and | ||
| 79 | reloads it on a short timer, so a push is live within a couple of seconds. | ||
| 80 | |||
| 81 | Only these paths are read; other files are ignored, so this README and any | ||
| 82 | notes can live here too. | ||
| 83 | |||
| 84 | profile/README.md the landing page introduction (markdown) | ||
| 85 | profile/private/README.md replaces it for signed-in viewers | ||
| 86 | profile/custom.css appended after the built-in stylesheets | ||
| 87 | profile/favicon.svg replaces the built-in favicon | ||
| 88 | assets/ served at /static/custom/assets/ | ||
| 89 | |||
| 90 | Edit a file here and push, or open it in the web interface and use the editor. | ||
| 91 | This repository has the usual pages, history, issues, and change requests. Its | ||
| 92 | visibility is a repository setting, so an instance admin can publish it or keep | ||
| 93 | it private. | ||
| 94 | |||
| 95 | The files above are served to visitors (the private README only to signed-in | ||
| 96 | viewers), even when the repository itself is private. | ||
| 97 | `), | ||
| 98 | "profile/README.md": []byte("# " + name + "\n\nTell people what this forge is for. This file is the landing page introduction.\n"), | ||
| 99 | "profile/custom.css": []byte(`/* Custom styles, loaded after the built-in stylesheets. | ||
| 100 | * | ||
| 101 | * The palette is CSS variables; override them here rather than copying rules: | ||
| 102 | * :root { | ||
| 103 | * --accent: #7fbbb3; | ||
| 104 | * --bg: #1e2326; | ||
| 105 | * } | ||
| 106 | * | ||
| 107 | * The layout is grid and flex, so most re-skins need only values, not selectors. | ||
| 108 | */ | ||
| 109 | `), | ||
| 110 | } | ||
| 111 | } | ||