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.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 19m ago
4b8c9e1d
| 1 | {{define "accounts_body"}} |
| 2 | {{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}} |
| 3 | |
| 4 | <p class="muted">To change a role, inspect keys or sessions, or retire an account, open it from the <a href="/-/u">users directory</a>.</p> |
| 5 | |
| 6 | <h2>Invite a person</h2> |
| 7 | <p class="muted">Send them a one-time link: they choose a passkey, then can add an SSH key.</p> |
| 8 | <form method="post" action="/-/users" class="form-block"> |
| 9 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 10 | <div class="card form-card"> |
| 11 | <label>Account name |
| 12 | <input type="text" name="name" required maxlength="63" autocomplete="off" placeholder="lowercase-name"> |
| 13 | </label> |
| 14 | {{template "role-picker" (rolePicker "role" "none" "Instance role" "Instance roles apply to public repositories; admin applies everywhere." "instance" false)}} |
| 15 | </div> |
| 16 | <div class="form-actions"><button type="submit">{{icon "user"}}Create account</button></div> |
| 17 | </form> |
| 18 | |
| 19 | <h2>Create a bot account</h2> |
| 20 | <p class="muted">For a runner or another automation. It acts with the SSH key you paste here and cannot sign in to the browser or become an instance admin.</p> |
| 21 | <form method="post" action="/-/users/bots" class="form-block"> |
| 22 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 23 | <div class="card form-card"> |
| 24 | <label>Account name |
| 25 | <input type="text" name="name" required maxlength="63" autocomplete="off" placeholder="lowercase-name"> |
| 26 | </label> |
| 27 | <label for="bot-public-key">SSH public key</label> |
| 28 | <textarea id="bot-public-key" name="key" rows="3" placeholder="ssh-ed25519 AAAA... you@example.com" required></textarea> |
| 29 | {{template "role-picker" (rolePicker "role" "none" "Instance role" "Instance roles apply to public repositories. A bot cannot be an instance admin." "bot" false)}} |
| 30 | </div> |
| 31 | <div class="form-actions"><button type="submit">{{icon "terminal"}}Create bot</button></div> |
| 32 | </form> |
| 33 | {{end}} |
| 34 | |
| 35 | {{define "admin_user"}} |
| 36 | {{template "header" .}} |
| 37 | <div class="page-head"> |
| 38 | <div class="page-head-main"> |
| 39 | <h1>{{.Account.Name}}{{if .Account.Bot}} <span class="state">bot</span>{{end}}</h1> |
| 40 | <p class="page-sub"> |
| 41 | <span class="state {{.Account.Status}}">{{.Account.Status}}</span> |
| 42 | · instance role {{.Account.Role}} |
| 43 | · created {{since .Account.Created.Unix}} |
| 44 | </p> |
| 45 | </div> |
| 46 | {{if not .Account.Bot}} |
| 47 | <div class="page-head-actions"> |
| 48 | <form class="inline" method="post" action="/-/users/{{.Account.Name}}/invite"> |
| 49 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 50 | <button type="submit" class="secondary">{{icon "external-link"}}New enrollment link</button> |
| 51 | </form> |
| 52 | </div> |
| 53 | {{end}} |
| 54 | </div> |
| 55 | |
| 56 | {{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}} |
| 57 | |
| 58 | <h2>Instance role</h2> |
| 59 | {{$role := .Account.Role.String}} |
| 60 | <form method="post" action="/-/users/{{.Account.Name}}/role" class="form-block"> |
| 61 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 62 | <div class="card form-card"> |
| 63 | {{template "role-picker" (rolePicker "role" $role (printf "Role for %s" .Account.Name) .RoleHelp .RoleScope false)}} |
| 64 | </div> |
| 65 | <div class="form-actions"> |
| 66 | <button type="submit">{{icon "check"}}Save role</button> |
| 67 | </div> |
| 68 | </form> |
| 69 | |
| 70 | <h2>SSH keys</h2> |
| 71 | {{if .Account.Keys}} |
| 72 | <div class="panel"> |
| 73 | <table> |
| 74 | <thead><tr><th>Fingerprint</th><th>Type</th><th>Comment</th><th></th></tr></thead> |
| 75 | <tbody> |
| 76 | {{range .Account.Keys}} |
| 77 | <tr> |
| 78 | <td><code>{{.Fingerprint}}</code></td> |
| 79 | <td>{{.Type}}</td> |
| 80 | <td class="muted">{{.Comment}}</td> |
| 81 | <td> |
| 82 | <form class="inline" method="post" action="/-/users/{{$.Account.Name}}/keys/remove"> |
| 83 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> |
| 84 | <input type="hidden" name="fingerprint" value="{{.Fingerprint}}"> |
| 85 | <button type="submit" class="danger">Remove</button> |
| 86 | </form> |
| 87 | </td> |
| 88 | </tr> |
| 89 | {{end}} |
| 90 | </tbody> |
| 91 | </table> |
| 92 | </div> |
| 93 | {{else if .Account.Bot}} |
| 94 | <div class="empty-state"><p>No SSH keys. This bot cannot act until a key is added.</p></div> |
| 95 | {{else}} |
| 96 | <div class="empty-state"><p>No SSH keys. This account can sign in with a passkey only.</p></div> |
| 97 | {{end}} |
| 98 | |
| 99 | <div class="with-more"> |
| 100 | <h2>Sessions</h2> |
| 101 | {{if .Sessions}} |
| 102 | <form class="inline" method="post" action="/-/users/{{.Account.Name}}/sessions/revoke"> |
| 103 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 104 | <button type="submit" class="link">Revoke all sessions</button> |
| 105 | </form> |
| 106 | {{end}} |
| 107 | </div> |
| 108 | {{if .Sessions}} |
| 109 | <div class="panel"> |
| 110 | <table> |
| 111 | <thead><tr><th>Created</th><th>Last seen</th><th>Expires</th><th>User agent</th></tr></thead> |
| 112 | <tbody> |
| 113 | {{range .Sessions}} |
| 114 | <tr> |
| 115 | <td class="nowrap">{{since .Created.Unix}}</td> |
| 116 | <td class="nowrap">{{since .LastSeen.Unix}}</td> |
| 117 | <td class="nowrap">{{.Expires.UTC.Format "2006-01-02 15:04"}}</td> |
| 118 | <td class="muted">{{.UserAgent}}</td> |
| 119 | </tr> |
| 120 | {{end}} |
| 121 | </tbody> |
| 122 | </table> |
| 123 | </div> |
| 124 | {{else}} |
| 125 | <div class="empty-state"><p>No browser sessions.</p></div> |
| 126 | {{end}} |
| 127 | |
| 128 | <h2>Danger zone</h2> |
| 129 | <form method="post" action="/-/users/{{.Account.Name}}/retire" class="form-block"> |
| 130 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 131 | <div class="card form-card danger-zone"> |
| 132 | <h3>Retire account</h3> |
| 133 | <p class="muted">Disables the account, ends its sessions, and closes any enrollment links. The name is never reissued.</p> |
| 134 | <label>Type <code>{{.Account.Name}}</code> to confirm |
| 135 | <input type="text" name="confirm" required autocomplete="off"> |
| 136 | </label> |
| 137 | </div> |
| 138 | <div class="form-actions"><button type="submit" class="danger">{{icon "circle-x"}}Retire account</button></div> |
| 139 | </form> |
| 140 | {{template "footer" .}} |
| 141 | {{end}} |
| 142 | |
| 143 | {{define "admin_invite"}} |
| 144 | {{template "header" .}} |
| 145 | <div class="page-head"> |
| 146 | <div class="page-head-main"> |
| 147 | <h1>Enrollment link</h1> |
| 148 | <p class="page-sub">For <strong>{{.Account.Name}}</strong> · expires {{.Expires.UTC.Format "2006-01-02 15:04 UTC"}}</p> |
| 149 | </div> |
| 150 | </div> |
| 151 | |
| 152 | {{if not .BrowserLogin}} |
| 153 | <div class="flash bad">Browser sign-in is disabled on this instance, so this link cannot be used until public_url is set to an HTTPS URL.</div> |
| 154 | {{end}} |
| 155 | |
| 156 | <div class="card"> |
| 157 | <p>Send this link to the account holder. It is shown once, and any earlier unused link for this account has been superseded.</p> |
| 158 | <p><code>{{.Link}}</code></p> |
| 159 | <p><button type="button" class="secondary" data-copy="{{.Link}}">{{icon "copy"}}Copy link</button></p> |
| 160 | </div> |
| 161 | |
| 162 | <div class="form-actions"> |
| 163 | <a class="button secondary" href="/-/users/{{.Account.Name}}">{{icon "user"}}Back to account</a> |
| 164 | </div> |
| 165 | {{template "footer" .}} |
| 166 | {{end}} |
| 167 |