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.
Identify accounts by their registered name
62576fa8157d431dcf2d360f3ebe481de77bda03 · Keenan Nemetz · 14d ago · parent 642e5994
Identify accounts by their registered name Drop the internal account id and display name. Users, keys, passkeys, invites, sessions, ACL entries, approvals, locks and CI records now all use the registered name as the single identity. The roster migrates in place: old id-keyed tables are rebuilt around the name, and access-rule/lock references are rewritten through the same mapping. Display-name editing and whoami --id are removed.
server/ssh/runner.go +15−19
| @@ -122,11 +122,10 @@ type runnerReply struct { | |||
| 122 | 122 | Run *runnerJob `json:"run,omitempty"` | |
| 123 | 123 | OK bool `json:"ok"` | |
| 124 | 124 | Lease int `json:"lease,omitempty"` | |
| 125 | // Who am I: the account this channel authenticated as. A runner answers "ping" with | ||
| 126 | // this so it can say whose results it will be reporting, and so a --repos filter can | ||
| 127 | // be checked against what the account may run. | ||
| 128 | Account string `json:"account,omitempty"` | ||
| 129 | Name string `json:"name,omitempty"` | ||
| 125 | // Who am I: the account name this channel authenticated as. A runner answers | ||
| 126 | // "ping" with this so it can say whose results it will be reporting, and so a | ||
| 127 | // --repos filter can be checked against what the account may run. | ||
| 128 | Name string `json:"name,omitempty"` | ||
| 130 | 129 | // Repos are the repositories this account may run jobs for, sent with "ping" so | |
| 131 | 130 | // a runner started with --repos can find out at startup that its filter matches | |
| 132 | 131 | // nothing — a typo it would otherwise discover as a permanently idle runner — | |
| @@ -135,7 +134,7 @@ type runnerReply struct { | |||
| 135 | 134 | } | |
| 136 | 135 | ||
| 137 | 136 | // serveRunner answers one runner session until it disconnects. | |
| 138 | func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | ||
| 137 | func (s *Server) serveRunner(ch xssh.Channel, user string) { | ||
| 139 | 138 | defer ch.Close() | |
| 140 | 139 | cfg := s.cfg | |
| 141 | 140 | if cfg.Runs == nil { | |
| @@ -144,7 +143,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 144 | 143 | } | |
| 145 | 144 | dec := json.NewDecoder(ch) | |
| 146 | 145 | enc := json.NewEncoder(ch) | |
| 147 | owner := accountID | ||
| 146 | owner := user | ||
| 148 | 147 | // Presence is one row per channel: a pool of three runners on one account is | |
| 149 | 148 | // three rows, and the row goes away when the channel does. Nothing else records | |
| 150 | 149 | // that a runner exists, so this is what the runners page shows. | |
| @@ -153,7 +152,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 153 | 152 | _ = enc.Encode(runnerReply{Op: "error", Error: err.Error()}) | |
| 154 | 153 | return | |
| 155 | 154 | } | |
| 156 | if err := cfg.Runs.TouchSession(sessionID, accountID, nil, time.Now().UTC()); err != nil { | ||
| 155 | if err := cfg.Runs.TouchSession(sessionID, user, nil, time.Now().UTC()); err != nil { | ||
| 157 | 156 | cfg.Log.Warn("runner: cannot record the session", "user", user, "err", err) | |
| 158 | 157 | } | |
| 159 | 158 | defer cfg.Runs.DropSession(sessionID) | |
| @@ -177,7 +176,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 177 | 176 | // on: running a job executes that repository's code, which is a bigger | |
| 178 | 177 | // power than writing to it. | |
| 179 | 178 | if reposAt.IsZero() || now.Sub(reposAt) > eligibleFor { | |
| 180 | list, err := s.runnableRepos(accountID) | ||
| 179 | list, err := s.runnableRepos(user) | ||
| 181 | 180 | if err != nil { | |
| 182 | 181 | _ = enc.Encode(runnerReply{Op: "lease", Error: err.Error()}) | |
| 183 | 182 | continue | |
| @@ -223,7 +222,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 223 | 222 | // say "waiting for work" instead of showing a runner that vanished. The | |
| 224 | 223 | // labels ride along, because a runner waiting for the wrong kind of work | |
| 225 | 224 | // is exactly what this row is for. | |
| 226 | _ = cfg.Runs.TouchSession(sessionID, accountID, req.Labels, time.Now().UTC()) | ||
| 225 | _ = cfg.Runs.TouchSession(sessionID, user, req.Labels, time.Now().UTC()) | ||
| 227 | 226 | _ = enc.Encode(runnerReply{Op: "lease", OK: true}) | |
| 228 | 227 | continue | |
| 229 | 228 | } | |
| @@ -254,7 +253,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 254 | 253 | case "heartbeat": | |
| 255 | 254 | // Liveness only: the lease is what a heartbeat buys. Presence is refreshed | |
| 256 | 255 | // here too, so a long build does not make its runner look gone. | |
| 257 | _ = cfg.Runs.TouchSession(sessionID, accountID, nil, time.Now().UTC()) | ||
| 256 | _ = cfg.Runs.TouchSession(sessionID, user, nil, time.Now().UTC()) | ||
| 258 | 257 | ok, err := cfg.Runs.Heartbeat(req.Run, owner, cfg.Lease, time.Now().UTC()) | |
| 259 | 258 | if err != nil { | |
| 260 | 259 | _ = enc.Encode(runnerReply{Op: "heartbeat", Error: err.Error()}) | |
| @@ -312,7 +311,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 312 | 311 | "the record for %s is attempt %d but this runner holds attempt %d", | |
| 313 | 312 | run.ID, rec.Attempt, run.Attempts)}) | |
| 314 | 313 | continue | |
| 315 | case rec.Signer != accountID: | ||
| 314 | case rec.Signer != user: | ||
| 316 | 315 | _ = enc.Encode(runnerReply{Op: "finish", Error: fmt.Sprintf( | |
| 317 | 316 | "the record for %s was reported by another account; %s cannot finish it", | |
| 318 | 317 | run.ID, user)}) | |
| @@ -374,13 +373,10 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 374 | 373 | _ = enc.Encode(runnerReply{Op: "finish", OK: ok}) | |
| 375 | 374 | ||
| 376 | 375 | case "ping": | |
| 377 | reply := runnerReply{Op: "ping", OK: true, Account: accountID} | ||
| 378 | if acct, aerr := cfg.Identity.Find(accountID); aerr == nil && acct != nil { | ||
| 379 | reply.Name = acct.Name | ||
| 380 | } | ||
| 376 | reply := runnerReply{Op: "ping", OK: true, Name: user} | ||
| 381 | 377 | // What this account may run, so a runner started with --repos can say at | |
| 382 | 378 | // startup that its filter matches nothing rather than idling in silence. | |
| 383 | if granted, gerr := s.runnableRepos(accountID); gerr == nil { | ||
| 379 | if granted, gerr := s.runnableRepos(user); gerr == nil { | ||
| 384 | 380 | reply.Repos = granted | |
| 385 | 381 | } else { | |
| 386 | 382 | cfg.Log.Warn("runner: cannot list runnable repositories", "user", user, "err", gerr) | |
| @@ -394,7 +390,7 @@ func (s *Server) serveRunner(ch xssh.Channel, user, accountID string) { | |||
| 394 | 390 | } | |
| 395 | 391 | ||
| 396 | 392 | // runnableRepos lists the repositories this account may execute jobs for. | |
| 397 | func (s *Server) runnableRepos(accountID string) ([]string, error) { | ||
| 393 | func (s *Server) runnableRepos(name string) ([]string, error) { | ||
| 398 | 394 | repos, err := s.cfg.Repos.List() | |
| 399 | 395 | if err != nil { | |
| 400 | 396 | return nil, err | |
| @@ -409,7 +405,7 @@ func (s *Server) runnableRepos(accountID string) ([]string, error) { | |||
| 409 | 405 | // job executes that repository's code, and a result from an account the | |
| 410 | 406 | // repository will not count is only worth refusing here rather than after | |
| 411 | 407 | // the build. | |
| 412 | if a.CanReport(accountID) { | ||
| 408 | if a.CanReport(name) { | ||
| 413 | 409 | out = append(out, m.Name) | |
| 414 | 410 | } | |
| 415 | 411 | } | |