s
This commit is contained in:
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user