package adminrpc
import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/admin"
)
type Server struct {
ops *admin.Admin
socketPath string
}
func NewServer(ops *admin.Admin, socketPath string) *Server {
return &Server{ops: ops, socketPath: socketPath}
}
// ListenAndServe serves admin RPC requests on the Unix socket until it
// errors. Only the owning user can connect (socket mode 0600) since this
// grants full admin control of the instance.
func (s *Server) ListenAndServe() error {
_ = os.Remove(s.socketPath) // safe: bbolt's exclusive lock means we're the only server for this data dir
l, err := net.Listen("unix", s.socketPath)
if err != nil {
return fmt.Errorf("adminrpc: listen %s: %w", s.socketPath, err)
}
if err := os.Chmod(s.socketPath, 0600); err != nil {
return fmt.Errorf("adminrpc: chmod %s: %w", s.socketPath, err)
}
log.Printf("gitfed admin socket listening on %s", s.socketPath)
for {
conn, err := l.Accept()
if err != nil {
return err
}
go s.handleConn(conn)
}
}
func (s *Server) handleConn(conn net.Conn) {
defer conn.Close()
var req wireRequest
if err := json.NewDecoder(conn).Decode(&req); err != nil {
return
}
result, err := s.dispatch(req)
resp := response{Result: result}
if err != nil {
resp.Error = err.Error()
}
_ = json.NewEncoder(conn).Encode(resp)
}
type wireRequest struct {
Method string `json:"method"`
Args json.RawMessage `json:"args,omitempty"`
}
func (s *Server) dispatch(req wireRequest) (any, error) {
switch req.Method {
case methodListUsers:
users, err := s.ops.ListUsers()
return listUsersResult{Users: users}, err
case methodCreateUser:
var a userKeyArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.CreateUser(a.Username, a.PubKey)
case methodAddUserKey:
var a userKeyArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.AddUserKey(a.Username, a.PubKey)
case methodRemoveUserKey:
var a userKeyArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.RemoveUserKey(a.Username, a.PubKey)
case methodDeleteUser:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.DeleteUser(a.Name)
case methodListRepos:
repos, err := s.ops.ListRepos()
return listReposResult{Repos: repos}, err
case methodGetRepo:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return s.ops.GetRepo(a.Name)
case methodCreateRepo:
var a createRepoArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.CreateRepo(a.Name, a.Owner)
case methodImportRepo:
var a importRepoArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.ImportRepo(a.Name, a.SourceURL, a.Owner)
case methodDeleteRepo:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.DeleteRepo(a.Name)
case methodGetACL:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return s.ops.GetACL(a.Name)
case methodGrantCollaborator:
var a collaboratorArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.GrantCollaborator(a.Repo, a.Principal, a.Actor, a.Role)
case methodRevokeCollaborator:
var a collaboratorArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.RevokeCollaborator(a.Repo, a.Principal)
case methodListTrustedCAs:
trust, err := s.ops.ListTrustedCAs()
return listTrustResult{Trust: trust}, err
case methodCountPendingTrust:
count, err := s.ops.CountPendingTrust()
return countResult{Count: count}, err
case methodApproveDomain:
var a domainArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.ApproveDomain(a.Domain)
case methodListAudit:
var a limitArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
events, err := s.ops.ListAudit(a.Limit)
return listAuditResult{Events: events}, err
case methodSetRepoPublic:
var a setPublicArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetRepoPublic(a.Name, a.Public)
case methodSetRepoTopics:
var a setTopicsArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetRepoTopics(a.Name, a.Topics)
case methodSetRepoDescription:
var a setDescriptionArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetRepoDescription(a.Name, a.Description)
case methodGetRepoReadme:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
content, found, err := s.ops.GetRepoReadme(a.Name)
return readmeResult{Content: content, Found: found}, err
case methodListReadmeLangs:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
langs, err := s.ops.ListRepoReadmeLanguages(a.Name)
return readmeLangsResult{Langs: langs}, err
case methodGetRepoReadmeLang:
var a nameLangArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
content, found, err := s.ops.GetRepoReadmeLang(a.Name, a.Lang)
return readmeResult{Content: content, Found: found}, err
case methodGetRepoLicense:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
content, filename, found, err := s.ops.GetRepoLicense(a.Name)
return licenseResult{Content: content, Filename: filename, Found: found}, err
case methodListRepoTags:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
tags, err := s.ops.ListRepoTags(a.Name)
return listTagsResult{Tags: tags}, err
case methodGetRepoArchive:
var a archiveArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
data, err := s.ops.GetRepoArchive(a.Name, a.Ref, a.Format)
return archiveResult{Data: data}, err
case methodListRepoTree:
var a pathArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
entries, found, err := s.ops.ListRepoTree(a.Name, a.Path)
return listTreeResult{Entries: entries, Found: found}, err
case methodListRepoTreeAtRef:
var a pathRefArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
entries, found, err := s.ops.ListRepoTreeAtRef(a.Name, a.Ref, a.Path)
return listTreeResult{Entries: entries, Found: found}, err
case methodGetRepoFile:
var a pathArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
content, found, err := s.ops.GetRepoFile(a.Name, a.Path)
return fileResult{Content: content, Found: found}, err
case methodGetRepoFileAtRef:
var a pathRefArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
content, found, err := s.ops.GetRepoFileAtRef(a.Name, a.Ref, a.Path)
return fileResult{Content: content, Found: found}, err
case methodGetRepoBranch:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
branch, found, err := s.ops.GetRepoBranch(a.Name)
return branchResult{Branch: branch, Found: found}, err
case methodCountCommits:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
count, found, err := s.ops.CountCommits(a.Name)
return countFoundResult{Count: count, Found: found}, err
case methodCountContributors:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
count, found, err := s.ops.CountContributors(a.Name)
return countFoundResult{Count: count, Found: found}, err
case methodDominantLanguage:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
lang, found, err := s.ops.DominantLanguage(a.Name)
return languageResult{Language: lang, Found: found}, err
case methodRepoDiskUsage:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
bytes, err := s.ops.RepoDiskUsage(a.Name)
return diskUsageResult{Bytes: bytes}, err
case methodGetUser:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
user, err := s.ops.GetUser(a.Name)
return userResult{User: user}, err
case methodSetUserAdmin:
var a setAdminArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetUserAdmin(a.Username, a.IsAdmin)
case methodSetUserBio:
var a setBioArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetUserBio(a.Username, a.Bio)
case methodSetPassword:
var a setPasswordArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.SetPassword(a.Username, a.Password)
case methodVerifyPassword:
var a setPasswordArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
isAdmin, ok, err := s.ops.VerifyPassword(a.Username, a.Password)
return verifyPasswordResult{IsAdmin: isAdmin, OK: ok}, err
case methodCreateSession:
var a createSessionArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
token, err := s.ops.CreateSession(a.Principal, a.Username, a.IsAdmin)
return createSessionResult{Token: token}, err
case methodGetSession:
var a tokenArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
sess, err := s.ops.GetSession(a.Token)
return sessionResult{Session: sess}, err
case methodDeleteSession:
var a tokenArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.DeleteSession(a.Token)
case methodCheckAccess:
var a checkAccessArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
role, ok, err := s.ops.CheckAccess(a.Repo, a.Principal, a.Want)
return checkAccessResult{Role: role, OK: ok}, err
case methodListCommits:
var a nameLimitArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
commits, found, err := s.ops.ListCommits(a.Name, a.Limit)
return listCommitsResult{Commits: commits, Found: found}, err
case methodListCommitsPage:
var a nameLimitOffsetArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
commits, found, err := s.ops.ListCommitsPage(a.Name, a.Limit, a.Offset)
return listCommitsResult{Commits: commits, Found: found}, err
case methodShowCommit:
var a nameHashArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
detail, found, err := s.ops.ShowCommit(a.Name, a.Hash)
return showCommitResult{Detail: detail, Found: found}, err
case methodCommitDiff:
var a nameHashArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
diff, truncated, err := s.ops.CommitDiff(a.Name, a.Hash)
return commitDiffResult{Diff: diff, Truncated: truncated}, err
case methodListBranches:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
branches, err := s.ops.ListBranches(a.Name)
return listBranchesResult{Branches: branches}, err
case methodCreateMergeRequest:
var a createMRArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
mr, err := s.ops.CreateMergeRequest(a.Repo, a.Title, a.Description, a.Author, a.SourceBranch, a.TargetBranch)
return mrResult{MR: mr}, err
case methodListMergeRequests:
var a nameArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
mrs, err := s.ops.ListMergeRequests(a.Name)
return listMRResult{MRs: mrs}, err
case methodGetMergeRequest:
var a mrArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
mr, err := s.ops.GetMergeRequest(a.Repo, a.Number)
return mrResult{MR: mr}, err
case methodMergeRequestDiff:
var a mrArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
files, diff, truncated, found, err := s.ops.MergeRequestDiff(a.Repo, a.Number)
return mrDiffResult{Files: files, Diff: diff, Truncated: truncated, Found: found}, err
case methodCheckMergeRequestMergeable:
var a mrArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
result, err := s.ops.CheckMergeRequestMergeable(a.Repo, a.Number)
return mergeResultRPC{Result: result}, err
case methodMergeMergeRequest:
var a mergeMRArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
mr, result, err := s.ops.MergeMergeRequest(a.Repo, a.Number, a.Actor)
return mergeMRResult{MR: mr, Result: result}, err
case methodCloseMergeRequest:
var a mrArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.CloseMergeRequest(a.Repo, a.Number)
case methodListMRComments:
var a mrArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
comments, err := s.ops.ListMRComments(a.Repo, a.Number)
return listMRCommentsResult{Comments: comments}, err
case methodAddMRComment:
var a mrCommentArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
comment, err := s.ops.AddMRComment(a.Repo, a.Number, a.Author, a.Body)
return addMRCommentResult{Comment: comment}, err
case methodPinRepo:
var a pinArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.PinRepo(a.Principal, a.Domain, a.Repo, a.Label)
case methodUnpinRepo:
var a pinArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.UnpinRepo(a.Principal, a.Domain, a.Repo)
case methodListPinnedRepos:
var a principalArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
pins, err := s.ops.ListPinnedRepos(a.Principal)
return listPinsResult{Pins: pins}, err
case methodListNotifications:
var a principalArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
notifs, err := s.ops.ListNotifications(a.Principal)
return listNotifsResult{Notifications: notifs}, err
case methodCountPendingNotifs:
var a principalArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
count, err := s.ops.CountPendingNotifications(a.Principal)
return countResult{Count: count}, err
case methodAcceptNotification:
var a notificationIDArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.AcceptNotification(a.Principal, a.ID)
case methodDismissNotification:
var a notificationIDArgs
if err := json.Unmarshal(req.Args, &a); err != nil {
return nil, err
}
return nil, s.ops.DismissNotification(a.Principal, a.ID)
default:
return nil, fmt.Errorf("adminrpc: unknown method %q", req.Method)
}
}