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.
Add browser auth, contributor access, and gated CI
6c84d387e5f608360e30484a1a5b1cff41076850 · Keenan Nemetz · 16d ago · parent c36d5073
Add browser auth, contributor access, and gated CI Browser sign-in now uses passkeys. Enrollment links are one-time, sessions are hashed in users.db, and issue/comment/approval writes use the session account. HTTPS public_url is required for WebAuthn. Add the contributor role between read and maintain. Contributors can discuss and open change requests, and may push only refs/heads/contrib/<account-id>/. Contributor CI is held per commit until a maintainer allows the exact tip from the repository page. Rename the write role to maintain. write remains accepted as the historical spelling, and access.Write remains as a Go alias so existing callers and ACL rows keep working. Update the web, CLI, queue schema, and guides for the new flows, and cover the role, namespace, auth, and CI-gating paths in tests.
forge/instance.go +16
| @@ -21,6 +21,7 @@ import ( | |||
| 21 | 21 | "github.com/rodent-software/vole/forge/identity" | |
| 22 | 22 | "github.com/rodent-software/vole/forge/index" | |
| 23 | 23 | "github.com/rodent-software/vole/forge/repo" | |
| 24 | "github.com/rodent-software/vole/forge/session" | ||
| 24 | 25 | ) | |
| 25 | 26 | ||
| 26 | 27 | // Instance is the state a running or administered Vole instance owns. | |
| @@ -28,6 +29,7 @@ type Instance struct { | |||
| 28 | 29 | Cfg *config.Config | |
| 29 | 30 | Repos *repo.Store | |
| 30 | 31 | Identity *identity.Store | |
| 32 | Sessions *session.Store | ||
| 31 | 33 | Index *index.Store | |
| 32 | 34 | Runs *runs.Store | |
| 33 | 35 | ACLs *access.Store | |
| @@ -51,14 +53,23 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) { | |||
| 51 | 53 | } | |
| 52 | 54 | ident.AuditPath = cfg.AuditLog() | |
| 53 | 55 | ||
| 56 | sessions, err := session.Open(cfg.UsersDB()) | ||
| 57 | if err != nil { | ||
| 58 | ident.Close() | ||
| 59 | return nil, fmt.Errorf("open sessions: %w", err) | ||
| 60 | } | ||
| 61 | sessions.AuditPath = cfg.AuditLog() | ||
| 62 | |||
| 54 | 63 | ix, err := index.Open(cfg.IndexDB()) | |
| 55 | 64 | if err != nil { | |
| 65 | sessions.Close() | ||
| 56 | 66 | ident.Close() | |
| 57 | 67 | return nil, fmt.Errorf("open index: %w", err) | |
| 58 | 68 | } | |
| 59 | 69 | rn, err := runs.Open(cfg.RunsDB()) | |
| 60 | 70 | if err != nil { | |
| 61 | 71 | ix.Close() | |
| 72 | sessions.Close() | ||
| 62 | 73 | ident.Close() | |
| 63 | 74 | return nil, fmt.Errorf("open run queue: %w", err) | |
| 64 | 75 | } | |
| @@ -66,6 +77,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) { | |||
| 66 | 77 | if err != nil { | |
| 67 | 78 | rn.Close() | |
| 68 | 79 | ix.Close() | |
| 80 | sessions.Close() | ||
| 69 | 81 | ident.Close() | |
| 70 | 82 | return nil, fmt.Errorf("open access rules: %w", err) | |
| 71 | 83 | } | |
| @@ -76,6 +88,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) { | |||
| 76 | 88 | acls.Close() | |
| 77 | 89 | rn.Close() | |
| 78 | 90 | ix.Close() | |
| 91 | sessions.Close() | ||
| 79 | 92 | ident.Close() | |
| 80 | 93 | return nil, fmt.Errorf("open locks: %w", err) | |
| 81 | 94 | } | |
| @@ -89,6 +102,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) { | |||
| 89 | 102 | acls.Close() | |
| 90 | 103 | rn.Close() | |
| 91 | 104 | ix.Close() | |
| 105 | sessions.Close() | ||
| 92 | 106 | ident.Close() | |
| 93 | 107 | return nil, err | |
| 94 | 108 | } | |
| @@ -97,6 +111,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) { | |||
| 97 | 111 | Cfg: cfg, | |
| 98 | 112 | Repos: rs, | |
| 99 | 113 | Identity: ident, | |
| 114 | Sessions: sessions, | ||
| 100 | 115 | Index: ix, | |
| 101 | 116 | Runs: rn, | |
| 102 | 117 | ACLs: acls, | |
| @@ -130,6 +145,7 @@ func (i *Instance) Close() error { | |||
| 130 | 145 | i.ACLs.Close, | |
| 131 | 146 | i.Runs.Close, | |
| 132 | 147 | i.Index.Close, | |
| 148 | i.Sessions.Close, | ||
| 133 | 149 | i.Identity.Close, | |
| 134 | 150 | } { | |
| 135 | 151 | if closeFn == nil { | |