This commit is contained in:
Jabberwocky238
2026-07-16 08:33:38 -04:00
parent 3ed6a7d804
commit 3de46894d0
6 changed files with 100 additions and 43 deletions
+67
View File
@@ -0,0 +1,67 @@
package common
import (
"errors"
"fmt"
"os"
"path/filepath"
"sync/atomic"
)
// Config carries the daemon's startup params. The server needs the git root to
// open repos and the rest to answer the Status RPC (which also hands gitctl the
// db_uri so it can connect directly for inspection reads).
type Config struct {
Root string
RPCAddr string
SSHAddr string
ManageAddr string
AuthURL string
DBUri string
SkipAuth bool
prepared atomic.Bool
driver string
dsn string
}
func (c *Config) RepoRoot() string {
return filepath.Join(c.Root, "repos")
}
// Prepare checks that the config is valid and creates the repo root if needed.
func (c *Config) Prepare() error {
var err error
if c.DBUri == "" && c.SkipAuth == false {
return errors.New("simplegit: -db is required unless -skip-auth (e.g. sqlite://state.db)")
}
if err := os.MkdirAll(c.RepoRoot(), 0o755); err != nil {
return fmt.Errorf("simplegit: Failed to create data root: %s (%v)", c.RepoRoot(), err)
}
c.driver, c.dsn, c.DBUri, err = ParseDBURI(c.Root, c.DBUri)
if err != nil {
return errors.New("simplegit: ParseDBURI failed")
}
if c.RPCAddr == "" {
return errors.New("simplegit: -rpc is required (the HTTP server is this binary's only listener)")
}
if c.ManageAddr == "" {
return fmt.Errorf("*manageAddr == nil")
}
c.prepared.Store(true)
return nil
}
func (c *Config) Driver() string {
if !c.prepared.Load() {
panic("Config.Driver called before Prepare")
}
return c.driver
}
func (c *Config) DSN() string {
if !c.prepared.Load() {
panic("Config.DSN called before Prepare")
}
return c.dsn
}
+4 -4
View File
@@ -277,10 +277,10 @@ message ACLDeleteRequest {
// directly for inspection reads; it carries credentials when postgres, so the // directly for inspection reads; it carries credentials when postgres, so the
// manage port must stay localhost-only/trusted. // manage port must stay localhost-only/trusted.
message StatusResponse { message StatusResponse {
string repo_root = 1; // -root (git repo root) string root = 1; // -root (git root)
string rpc_addr = 2; // -rpc listen ("" if n/a) string rpc_addr = 2; // -rpc listen
string ssh_addr = 3; // -ssh listen ("" if disabled) string ssh_addr = 3; // -ssh listen
string manage_addr = 4; // -manage listen ("" if disabled) string manage_addr = 4; // -manage listen
string auth_url = 5; // -auth RBAC center ("" if skip-auth) string auth_url = 5; // -auth RBAC center ("" if skip-auth)
string db_uri = 6; // -db state DB URI string db_uri = 6; // -db state DB URI
bool skip_auth = 7; // -skip-auth bool skip_auth = 7; // -skip-auth
+3 -16
View File
@@ -20,28 +20,15 @@ import (
"simplegit/state" "simplegit/state"
) )
// Config carries the daemon's startup params. The server needs the git root to
// open repos and the rest to answer the Status RPC (which also hands gitctl the
// db_uri so it can connect directly for inspection reads).
type Config struct {
Root string
RPCAddr string
SSHAddr string
ManageAddr string
AuthURL string
DBUri string
SkipAuth bool
}
type Server struct { type Server struct {
gitRepoRoot string gitRepoRoot string
cfg Config cfg *common.Config
startedAt time.Time startedAt time.Time
middleware *MiddlewareClient middleware *MiddlewareClient
mut state.IStateMut mut state.IStateMut
} }
func NewServer(cfg Config, middleware *MiddlewareClient, mut state.IStateMut) *Server { func NewServer(cfg *common.Config, middleware *MiddlewareClient, mut state.IStateMut) *Server {
return &Server{ return &Server{
gitRepoRoot: cfg.Root, gitRepoRoot: cfg.Root,
cfg: cfg, cfg: cfg,
@@ -511,7 +498,7 @@ func (s *Server) ACLDelete(ctx context.Context, req *connect.Request[v1.ACLDelet
func (s *Server) Status(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) { func (s *Server) Status(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) {
return connect.NewResponse(&v1.StatusResponse{ return connect.NewResponse(&v1.StatusResponse{
RepoRoot: s.gitRepoRoot, Root: s.cfg.Root,
RpcAddr: s.cfg.RPCAddr, RpcAddr: s.cfg.RPCAddr,
SshAddr: s.cfg.SSHAddr, SshAddr: s.cfg.SSHAddr,
ManageAddr: s.cfg.ManageAddr, ManageAddr: s.cfg.ManageAddr,
+9 -9
View File
@@ -1472,10 +1472,10 @@ func (x *ACLDeleteRequest) GetAclId() int64 {
// manage port must stay localhost-only/trusted. // manage port must stay localhost-only/trusted.
type StatusResponse struct { type StatusResponse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
RepoRoot string `protobuf:"bytes,1,opt,name=repo_root,json=repoRoot,proto3" json:"repo_root,omitempty"` // -root (git repo root) Root string `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` // -root (git root)
RpcAddr string `protobuf:"bytes,2,opt,name=rpc_addr,json=rpcAddr,proto3" json:"rpc_addr,omitempty"` // -rpc listen ("" if n/a) RpcAddr string `protobuf:"bytes,2,opt,name=rpc_addr,json=rpcAddr,proto3" json:"rpc_addr,omitempty"` // -rpc listen
SshAddr string `protobuf:"bytes,3,opt,name=ssh_addr,json=sshAddr,proto3" json:"ssh_addr,omitempty"` // -ssh listen ("" if disabled) SshAddr string `protobuf:"bytes,3,opt,name=ssh_addr,json=sshAddr,proto3" json:"ssh_addr,omitempty"` // -ssh listen
ManageAddr string `protobuf:"bytes,4,opt,name=manage_addr,json=manageAddr,proto3" json:"manage_addr,omitempty"` // -manage listen ("" if disabled) ManageAddr string `protobuf:"bytes,4,opt,name=manage_addr,json=manageAddr,proto3" json:"manage_addr,omitempty"` // -manage listen
AuthUrl string `protobuf:"bytes,5,opt,name=auth_url,json=authUrl,proto3" json:"auth_url,omitempty"` // -auth RBAC center ("" if skip-auth) AuthUrl string `protobuf:"bytes,5,opt,name=auth_url,json=authUrl,proto3" json:"auth_url,omitempty"` // -auth RBAC center ("" if skip-auth)
DbUri string `protobuf:"bytes,6,opt,name=db_uri,json=dbUri,proto3" json:"db_uri,omitempty"` // -db state DB URI DbUri string `protobuf:"bytes,6,opt,name=db_uri,json=dbUri,proto3" json:"db_uri,omitempty"` // -db state DB URI
SkipAuth bool `protobuf:"varint,7,opt,name=skip_auth,json=skipAuth,proto3" json:"skip_auth,omitempty"` // -skip-auth SkipAuth bool `protobuf:"varint,7,opt,name=skip_auth,json=skipAuth,proto3" json:"skip_auth,omitempty"` // -skip-auth
@@ -1515,9 +1515,9 @@ func (*StatusResponse) Descriptor() ([]byte, []int) {
return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{23} return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{23}
} }
func (x *StatusResponse) GetRepoRoot() string { func (x *StatusResponse) GetRoot() string {
if x != nil { if x != nil {
return x.RepoRoot return x.Root
} }
return "" return ""
} }
@@ -3512,9 +3512,9 @@ const file_gitrpc_gitrpc_proto_rawDesc = "" +
"\x03pat\x18\x02 \x01(\tR\x03pat\x12\x12\n" + "\x03pat\x18\x02 \x01(\tR\x03pat\x12\x12\n" +
"\x04perm\x18\x03 \x01(\tR\x04perm\")\n" + "\x04perm\x18\x03 \x01(\tR\x04perm\")\n" +
"\x10ACLDeleteRequest\x12\x15\n" + "\x10ACLDeleteRequest\x12\x15\n" +
"\x06acl_id\x18\x01 \x01(\x03R\x05aclId\"\xb5\x02\n" + "\x06acl_id\x18\x01 \x01(\x03R\x05aclId\"\xac\x02\n" +
"\x0eStatusResponse\x12\x1b\n" + "\x0eStatusResponse\x12\x12\n" +
"\trepo_root\x18\x01 \x01(\tR\brepoRoot\x12\x19\n" + "\x04root\x18\x01 \x01(\tR\x04root\x12\x19\n" +
"\brpc_addr\x18\x02 \x01(\tR\arpcAddr\x12\x19\n" + "\brpc_addr\x18\x02 \x01(\tR\arpcAddr\x12\x19\n" +
"\bssh_addr\x18\x03 \x01(\tR\asshAddr\x12\x1f\n" + "\bssh_addr\x18\x03 \x01(\tR\asshAddr\x12\x1f\n" +
"\vmanage_addr\x18\x04 \x01(\tR\n" + "\vmanage_addr\x18\x04 \x01(\tR\n" +
+11 -7
View File
@@ -27,6 +27,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"simplegit/common" "simplegit/common"
@@ -76,7 +77,7 @@ var (
type LocalState struct { type LocalState struct {
engine *xorm.Engine engine *xorm.Engine
root string cfg *common.Config
// skipAuth bypasses the DB: AccessBy* always allow, RepoPath resolves any // skipAuth bypasses the DB: AccessBy* always allow, RepoPath resolves any
// path under root. engine is nil in this mode. // path under root. engine is nil in this mode.
skipAuth bool skipAuth bool
@@ -92,17 +93,19 @@ func init() {
sql.Register(sqliteDriver, &sqlite.Driver{}) sql.Register(sqliteDriver, &sqlite.Driver{})
} }
func OpenWithDSN(root string, driver string, dsn string) (*LocalState, error) { func Open(cfg *common.Config) (*LocalState, error) {
engine, err := InitEngine(driver, dsn) engine, err := InitEngine(cfg.Driver(), cfg.DSN())
if err != nil { if err != nil {
return nil, fmt.Errorf("state: init engine: %w", err) return nil, fmt.Errorf("state: init engine: %w", err)
} }
if driver == sqliteDriver { if cfg.Driver() == sqliteDriver {
engine.SetMaxOpenConns(1) engine.SetMaxOpenConns(1)
} }
log.Printf("state: RepoRoot: %s", cfg.RepoRoot())
log.Printf("state: DB engine %s opened, %s", cfg.Driver(), cfg.DSN())
return &LocalState{ return &LocalState{
engine: engine, engine: engine,
root: root, cfg: cfg,
}, nil }, nil
} }
@@ -164,7 +167,7 @@ func (s *LocalState) loadOrCreateHostKey(ty string) (ssh.Signer, error) {
default: default:
return nil, errors.New("loadOrCreateHostKey") return nil, errors.New("loadOrCreateHostKey")
} }
hostKeyPath := filepath.Join(s.root, fmt.Sprintf("host_key_%v.pem", ty)) hostKeyPath := filepath.Join(s.cfg.Root, fmt.Sprintf("host_key_%v.pem", ty))
if data, err := os.ReadFile(hostKeyPath); err == nil { if data, err := os.ReadFile(hostKeyPath); err == nil {
return ssh.ParsePrivateKey(data) return ssh.ParsePrivateKey(data)
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
@@ -214,13 +217,14 @@ func (s *LocalState) findRepo(owner, name string) (*Repo, error) {
// repo is registered in the DB -- the DB is the authority, not the filesystem. // repo is registered in the DB -- the DB is the authority, not the filesystem.
// In skip-auth mode the DB check is skipped (any path under root resolves). // In skip-auth mode the DB check is skipped (any path under root resolves).
func (s *LocalState) RepoPath(owner, name string) (string, error) { func (s *LocalState) RepoPath(owner, name string) (string, error) {
name = normalizeName(name)
rel := relPath(owner, name) rel := relPath(owner, name)
if !s.skipAuth { if !s.skipAuth {
if _, err := s.findRepo(owner, name); err != nil { if _, err := s.findRepo(owner, name); err != nil {
return "", err return "", err
} }
} }
return common.Resolve(s.root, rel) return common.Resolve(s.cfg.RepoRoot(), rel)
} }
// Open returns a gitcmd.Repository for owner/name. The repo must be registered // Open returns a gitcmd.Repository for owner/name. The repo must be registered
+6 -7
View File
@@ -46,11 +46,11 @@ func (s *LocalState) nsDir(ns string) (string, error) {
if ns == "" || strings.Contains(ns, "..") { if ns == "" || strings.Contains(ns, "..") {
return "", fmt.Errorf("invalid namespace %q", ns) return "", fmt.Errorf("invalid namespace %q", ns)
} }
abs, err := filepath.Abs(filepath.Join(s.root, ns)) abs, err := filepath.Abs(filepath.Join(s.cfg.RepoRoot(), ns))
if err != nil { if err != nil {
return "", err return "", err
} }
rootAbs, err := filepath.Abs(s.root) rootAbs, err := filepath.Abs(s.cfg.RepoRoot())
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -106,8 +106,7 @@ func (s *LocalState) nextNamespaceID() (int64, error) {
// namespace must already exist (have at least one repo) so NamespaceID can be // namespace must already exist (have at least one repo) so NamespaceID can be
// resolved; NameID is freshly assigned. skip-auth: disk-only (git init, no DB). // resolved; NameID is freshly assigned. skip-auth: disk-only (git init, no DB).
func (s *LocalState) CreateRepo(ns, name string) error { func (s *LocalState) CreateRepo(ns, name string) error {
name = normalizeName(name) path, err := s.RepoPath(ns, name)
path, err := common.Resolve(s.root, relPath(ns, name))
if err != nil { if err != nil {
return err return err
} }
@@ -164,7 +163,7 @@ func (s *LocalState) ExistRepo(ns, name string) error {
// DeleteRepo removes the repo row, its repo-targeted ACL grants, and the // DeleteRepo removes the repo row, its repo-targeted ACL grants, and the
// on-disk bare directory. Hard-deletes. skip-auth: disk-only. // on-disk bare directory. Hard-deletes. skip-auth: disk-only.
func (s *LocalState) DeleteRepo(ns, name string) error { func (s *LocalState) DeleteRepo(ns, name string) error {
path, _ := common.Resolve(s.root, relPath(ns, name)) path, _ := s.RepoPath(ns, name)
if s.skipAuth { if s.skipAuth {
if path != "" { if path != "" {
_ = os.RemoveAll(path) _ = os.RemoveAll(path)
@@ -199,11 +198,11 @@ func (s *LocalState) MoveRepo(old, new string) error {
if err != nil { if err != nil {
return err return err
} }
oldPath, err := common.Resolve(s.root, relPath(oldNs, oldName)) oldPath, err := s.RepoPath(oldNs, oldName)
if err != nil { if err != nil {
return err return err
} }
newPath, err := common.Resolve(s.root, relPath(newNs, newName)) newPath, err := s.RepoPath(newNs, newName)
if err != nil { if err != nil {
return err return err
} }