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.

Add remote administration and local workflow runs

3abbe749096f4d7ff76f4d468e4427829b8a6f95 · Keenan Nemetz · 16d ago · parent 520470e1

Add remote administration and local workflow runs

Make `vole admin` the single namespace for user, repository, and access
management, runnable locally or over SSH by instance admins. Instance-admin
grants live in users.db, and the audit log names the actor separately from the
account being changed.

Add `vole workflow run` to execute workflow jobs in the working tree without a
server or runner. Positional arguments select workflows, --job selects jobs, and
the command reuses the push-time validator.

Update the README and guides for the new command shapes.

server/ssh/server.go +14−5

@@ -28,6 +28,7 @@ import (
28 28
29 29 "github.com/rodent-software/vole/ci/runs"
30 30 "github.com/rodent-software/vole/core/lfs"
31 "github.com/rodent-software/vole/forge"
31 32 "github.com/rodent-software/vole/forge/identity"
32 33 "github.com/rodent-software/vole/forge/repo"
33 34 )
@@ -37,6 +38,7 @@ const serverVersion = "SSH-2.0-vole"
37 38 type Config struct {
38 39 Identity *identity.Store
39 40 Repos *repo.Store
41 Instance *forge.Instance
40 42 Log *slog.Logger
41 43 HostKeyPath string
42 44 MaxBlobBytes int64
@@ -165,18 +167,18 @@ func (s *Server) handleConn(c net.Conn) {
165 167 if err != nil {
166 168 continue
167 169 }
168 go s.handleSession(ch, chReqs, user, accountID, fp)
170 go s.handleSession(ch, chReqs, user, accountID, fp, sconn.RemoteAddr().String())
169 171 }
170 172 }
171 173
172 func (s *Server) handleSession(ch xssh.Channel, reqs <-chan *xssh.Request, user, accountID, fp string) {
174 func (s *Server) handleSession(ch xssh.Channel, reqs <-chan *xssh.Request, user, accountID, fp, remote string) {
173 175 defer ch.Close()
174 176 for req := range reqs {
175 177 switch req.Type {
176 178 case "exec":
177 179 var payload struct{ Command string }
178 180 xssh.Unmarshal(req.Payload, &payload)
179 s.exec(ch, req, payload.Command, user, accountID, fp)
181 s.exec(ch, req, payload.Command, user, accountID, fp, remote)
180 182 return
181 183 case "shell", "pty-req", "subsystem":
182 184 req.Reply(false, nil)
@@ -192,7 +194,7 @@ func (s *Server) handleSession(ch xssh.Channel, reqs <-chan *xssh.Request, user,
192 194 }
193 195 }
194 196
195 func (s *Server) exec(ch xssh.Channel, req *xssh.Request, command, user, accountID, fp string) {
197 func (s *Server) exec(ch xssh.Channel, req *xssh.Request, command, user, accountID, fp, remote string) {
196 198 // A refusal is still answered on the channel rather than at the request
197 199 // layer: replying false makes the client print "exec request failed on
198 200 // channel 0" and the reason is lost. Nothing is ever executed on these
@@ -217,6 +219,13 @@ func (s *Server) exec(ch xssh.Channel, req *xssh.Request, command, user, account
217 219 exit(ch, 0)
218 220 return
219 221 }
222 // The admin API is a fixed command set, not a shell. It is available only to
223 // accounts marked as instance admins.
224 if len(argv) >= 1 && argv[0] == "vole-admin" {
225 req.Reply(true, nil)
226 exit(ch, s.serveAdmin(ch, argv[1:], user, accountID, remote))
227 return
228 }
220 229 // git-lfs speaks its own protocol over an SSH remote: the client runs
221 230 // `git-lfs-transfer <repo> upload|download` on the same connection family as
222 231 // the push. No token changes hands; the key is the identity.
@@ -251,7 +260,7 @@ func (s *Server) exec(ch xssh.Channel, req *xssh.Request, command, user, account
251 260 return
252 261 }
253 262 if len(argv) != 2 {
254 refuse("only git-upload-pack, git-receive-pack, vole-roster, vole-whoami and vole-runner are available")
263 refuse("only git-upload-pack, git-receive-pack, vole-admin, vole-roster, vole-whoami and vole-runner are available")
255 264 return
256 265 }
257 266 verb := argv[0]