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.
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.
server/web/admin_instance.go +4−64
| @@ -10,7 +10,7 @@ import ( | |||
| 10 | 10 | "strings" | |
| 11 | 11 | "time" | |
| 12 | 12 | ||
| 13 | "github.com/rodent-software/vole/core/git" | ||
| 13 | "github.com/rodent-software/vole/forge/instancerepo" | ||
| 14 | 14 | ) | |
| 15 | 15 | ||
| 16 | 16 | // instanceRepoView is the Instance repository panel on the Configuration tab. | |
| @@ -71,35 +71,10 @@ func (w *Web) CreateInstanceRepo(rw http.ResponseWriter, r *http.Request) { | |||
| 71 | 71 | w.serverError(rw, r, "the repository store is unavailable") | |
| 72 | 72 | return | |
| 73 | 73 | } | |
| 74 | created := false | ||
| 75 | m, err := w.Repos.Meta(name) | ||
| 74 | m, created, err := instancerepo.Ensure(w.Repos, name, pr.account.Name, pr.account.Name, w.instanceName()) | ||
| 76 | 75 | if err != nil { | |
| 77 | m, err = w.Repos.Create(name, pr.account.Name, "Instance customization: profile README, custom CSS, favicon, and assets") | ||
| 78 | if err != nil { | ||
| 79 | w.renderAdminTab(rw, r, "configuration", err.Error(), http.StatusBadRequest) | ||
| 80 | return | ||
| 81 | } | ||
| 82 | created = true | ||
| 83 | } | ||
| 84 | // An existing repository with no commits is still a repository to finish, so | ||
| 85 | // the button seeds it rather than failing with "already exists". | ||
| 86 | if tip, _ := m.Git().RevParseRaw("refs/heads/" + m.DefaultBranch); tip == "" { | ||
| 87 | g := m.Git() | ||
| 88 | tree, err := git.WriteFlatTree(g, instanceRepoTemplate(w.instanceName())) | ||
| 89 | if err != nil { | ||
| 90 | w.serverError(rw, r, "starter files could not be written: "+err.Error()) | ||
| 91 | return | ||
| 92 | } | ||
| 93 | commit, err := g.CommitTreeAs(tree, "", "Create the instance repository", pr.account.Name) | ||
| 94 | if err != nil { | ||
| 95 | w.serverError(rw, r, "starter files could not be committed: "+err.Error()) | ||
| 96 | return | ||
| 97 | } | ||
| 98 | if err := g.UpdateRef("refs/heads/"+m.DefaultBranch, commit); err != nil { | ||
| 99 | w.serverError(rw, r, "the branch could not be moved: "+err.Error()) | ||
| 100 | return | ||
| 101 | } | ||
| 102 | created = true | ||
| 76 | w.renderAdminTab(rw, r, "configuration", err.Error(), http.StatusBadRequest) | ||
| 77 | return | ||
| 103 | 78 | } | |
| 104 | 79 | if created && w.Index != nil { | |
| 105 | 80 | if err := w.Index.Upsert(m); err != nil { | |
| @@ -119,38 +94,3 @@ func (w *Web) CreateInstanceRepo(rw http.ResponseWriter, r *http.Request) { | |||
| 119 | 94 | w.audit("web.admin.instance.repo", pr.account.Name, name, "created") | |
| 120 | 95 | http.Redirect(rw, r, "/admin?tab=configuration&created=1", http.StatusSeeOther) | |
| 121 | 96 | } | |
| 122 | |||
| 123 | // instanceRepoTemplate is the starter tree. The root README explains the fixed | ||
| 124 | // paths; the profile files give the admin something to edit rather than a blank | ||
| 125 | // page, and the CSS names the variables worth overriding. | ||
| 126 | func instanceRepoTemplate(name string) map[string][]byte { | ||
| 127 | return map[string][]byte{ | ||
| 128 | "README.md": []byte(`# Instance customization | ||
| 129 | |||
| 130 | This repository customizes the Vole instance. Only these paths are read; other | ||
| 131 | files are ignored, so this README and any notes can live here too. | ||
| 132 | |||
| 133 | profile/README.md the landing page introduction (markdown) | ||
| 134 | profile/private/README.md replaces it for signed-in viewers | ||
| 135 | profile/custom.css appended after the built-in stylesheets | ||
| 136 | profile/favicon.svg replaces the built-in favicon | ||
| 137 | assets/ served at /static/custom/assets/ | ||
| 138 | |||
| 139 | Pushing to ` + "`main`" + ` updates the instance within a couple of seconds. The | ||
| 140 | repository is private to the instance's admins; the files above are served to | ||
| 141 | everyone (the private README only to signed-in viewers). | ||
| 142 | `), | ||
| 143 | "profile/README.md": []byte("# " + name + "\n\nTell people what this forge is for. This file is the landing page introduction.\n"), | ||
| 144 | "profile/custom.css": []byte(`/* Custom styles, loaded after the built-in stylesheets. | ||
| 145 | * | ||
| 146 | * The palette is CSS variables; override them here rather than copying rules: | ||
| 147 | * :root { | ||
| 148 | * --accent: #7fbbb3; | ||
| 149 | * --bg: #1e2326; | ||
| 150 | * } | ||
| 151 | * | ||
| 152 | * The layout is grid and flex, so most re-skins need only values, not selectors. | ||
| 153 | */ | ||
| 154 | `), | ||
| 155 | } | ||
| 156 | } | ||