Documentation
¶
Overview ¶
Package grpcserver provides the gRPC server implementation for Clonr.
The server exposes repository management operations via gRPC, allowing remote clients to interact with the repository database. It implements the ClonrService defined in the protobuf definitions.
Server Lifecycle ¶
The server is started via NewServer which creates a gRPC server with health checking, logging, recovery, and timeout interceptors:
srv := grpcserver.NewServer(db) srv.GRPCServer.Serve(listener)
Server Discovery ¶
When the server starts, it writes a server.json file containing connection information (address, port, PID) to the user's cache directory. This allows clients to automatically discover running servers without configuration.
The server info file locations are platform-specific:
- Windows: %LOCALAPPDATA%\clonr\server.json
- Linux: ~/.cache/clonr/server.json
- macOS: ~/Library/Caches/clonr/server.json
Duplicate Prevention ¶
Before starting, the server checks if another instance is already running using IsServerRunning. This function reads the server.json file and verifies the PID is a running clonr process using goprocess.
Interceptors ¶
The server includes three interceptors:
- Logging: Logs all RPC calls with method name, status, and duration
- Recovery: Catches panics and converts them to gRPC errors
- Timeout: Enforces a 30-second timeout on all requests
Index ¶
- Variables
- func CanceledError() error
- func InternalError(msg string) error
- func InternalErrorf(format string, args ...any) error
- func IsClonrProcessRunning(pid int) bool
- func ModelToProtoConfig(cfg *model.Config) *v1.Config
- func ModelToProtoProfile(profile *model.Profile) *v1.Profile
- func ModelToProtoRepository(repo *model.Repository) *v1.Repository
- func ModelToProtoWorkspace(workspace *model.Workspace) *v1.Workspace
- func NotFoundError(resourceType string) error
- func NotNil(value any, fieldName string) error
- func ProtoToModelConfig(protoCfg *v1.Config) *model.Config
- func ProtoToModelProfile(protoProfile *v1.Profile) *model.Profile
- func ProtoToModelRepository(protoRepo *v1.Repository) model.Repository
- func ProtoToModelWorkspace(protoWorkspace *v1.Workspace) *model.Workspace
- func RemoveServerInfo()
- func RequiredName(value string) error
- func RequiredOneOf(fields map[string]string) error
- func RequiredPath(value string) error
- func RequiredString(value, fieldName string) error
- func RequiredURL(value string) error
- func WriteServerInfo(port int) error
- type IdleTracker
- type RotationScheduler
- type ServerInfo
- type ServerWithHealth
- type Service
- func (s *Service) DeleteProfile(_ context.Context, req *v1.DeleteProfileRequest) (*v1.DeleteProfileResponse, error)
- func (s *Service) DeleteWorkspace(_ context.Context, req *v1.DeleteWorkspaceRequest) (*v1.DeleteWorkspaceResponse, error)
- func (s *Service) GetActiveProfile(_ context.Context, _ *v1.GetActiveProfileRequest) (*v1.GetActiveProfileResponse, error)
- func (s *Service) GetActiveWorkspace(_ context.Context, _ *v1.GetActiveWorkspaceRequest) (*v1.GetActiveWorkspaceResponse, error)
- func (s *Service) GetAllRepos(_ context.Context, _ *v1.GetAllReposRequest) (*v1.GetAllReposResponse, error)
- func (s *Service) GetConfig(_ context.Context, _ *v1.GetConfigRequest) (*v1.GetConfigResponse, error)
- func (s *Service) GetProfile(_ context.Context, req *v1.GetProfileRequest) (*v1.GetProfileResponse, error)
- func (s *Service) GetRepos(_ context.Context, req *v1.GetReposRequest) (*v1.GetReposResponse, error)
- func (s *Service) GetReposByWorkspace(_ context.Context, req *v1.GetReposByWorkspaceRequest) (*v1.GetReposByWorkspaceResponse, error)
- func (s *Service) GetWorkspace(_ context.Context, req *v1.GetWorkspaceRequest) (*v1.GetWorkspaceResponse, error)
- func (s *Service) InsertRepoIfNotExists(_ context.Context, req *v1.InsertRepoIfNotExistsRequest) (*v1.InsertRepoIfNotExistsResponse, error)
- func (s *Service) ListProfiles(_ context.Context, _ *v1.ListProfilesRequest) (*v1.ListProfilesResponse, error)
- func (s *Service) ListWorkspaces(_ context.Context, _ *v1.ListWorkspacesRequest) (*v1.ListWorkspacesResponse, error)
- func (s *Service) Ping(ctx context.Context, req *v1.Empty) (*v1.Empty, error)
- func (s *Service) ProfileExists(_ context.Context, req *v1.ProfileExistsRequest) (*v1.ProfileExistsResponse, error)
- func (s *Service) RemoveRepoByURL(_ context.Context, req *v1.RemoveRepoByURLRequest) (*v1.RemoveRepoByURLResponse, error)
- func (s *Service) RepoExistsByPath(_ context.Context, req *v1.RepoExistsByPathRequest) (*v1.RepoExistsByPathResponse, error)
- func (s *Service) RepoExistsByURL(_ context.Context, req *v1.RepoExistsByURLRequest) (*v1.RepoExistsByURLResponse, error)
- func (s *Service) SaveConfig(_ context.Context, req *v1.SaveConfigRequest) (*v1.SaveConfigResponse, error)
- func (s *Service) SaveProfile(_ context.Context, req *v1.SaveProfileRequest) (*v1.SaveProfileResponse, error)
- func (s *Service) SaveRepo(_ context.Context, req *v1.SaveRepoRequest) (*v1.SaveRepoResponse, error)
- func (s *Service) SaveWorkspace(_ context.Context, req *v1.SaveWorkspaceRequest) (*v1.SaveWorkspaceResponse, error)
- func (s *Service) SetActiveProfile(_ context.Context, req *v1.SetActiveProfileRequest) (*v1.SetActiveProfileResponse, error)
- func (s *Service) SetActiveWorkspace(_ context.Context, req *v1.SetActiveWorkspaceRequest) (*v1.SetActiveWorkspaceResponse, error)
- func (s *Service) SetFavoriteByURL(_ context.Context, req *v1.SetFavoriteRequest) (*v1.SetFavoriteResponse, error)
- func (s *Service) UpdateRepoTimestamp(_ context.Context, req *v1.UpdateRepoTimestampRequest) (*v1.UpdateRepoTimestampResponse, error)
- func (s *Service) UpdateRepoWorkspace(_ context.Context, req *v1.UpdateRepoWorkspaceRequest) (*v1.UpdateRepoWorkspaceResponse, error)
- func (s *Service) WorkspaceExists(_ context.Context, req *v1.WorkspaceExistsRequest) (*v1.WorkspaceExistsResponse, error)
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoServerInfo indicates no server info file exists ErrNoServerInfo = errors.New("no server info file") )
Functions ¶
func CanceledError ¶
func CanceledError() error
CanceledError returns a standard request canceled error.
func InternalError ¶
InternalError returns an internal error with the given message.
func InternalErrorf ¶
InternalErrorf returns an internal error with formatted message.
func IsClonrProcessRunning ¶
IsClonrProcessRunning checks if a clonr server with the given PID is running. Uses goprocess to verify it's actually a Go process with clonr executable.
func ModelToProtoConfig ¶
ModelToProtoConfig converts a model.Config to a proto Config
func ModelToProtoProfile ¶
ModelToProtoProfile converts a model.Profile to a proto Profile
func ModelToProtoRepository ¶
func ModelToProtoRepository(repo *model.Repository) *v1.Repository
ModelToProtoRepository converts a model.Repository to a proto Repository
func ModelToProtoWorkspace ¶
ModelToProtoWorkspace converts a model.Workspace to a proto Workspace
func NotFoundError ¶
NotFoundError returns a standard not found error with the resource type.
func NotNil ¶
NotNil validates that a pointer/interface is not nil. Returns nil if valid, or an InvalidArgument error with the field name.
func ProtoToModelConfig ¶
ProtoToModelConfig converts a proto Config to a model.Config
func ProtoToModelProfile ¶
ProtoToModelProfile converts a proto Profile to a model.Profile
func ProtoToModelRepository ¶
func ProtoToModelRepository(protoRepo *v1.Repository) model.Repository
ProtoToModelRepository converts a proto Repository to a model.Repository
func ProtoToModelWorkspace ¶
ProtoToModelWorkspace converts a proto Workspace to a model.Workspace
func RemoveServerInfo ¶
func RemoveServerInfo()
RemoveServerInfo removes the server info file (called when the server stops)
func RequiredName ¶
RequiredName validates that a name string is not empty. Returns nil if valid, or an InvalidArgument error.
func RequiredOneOf ¶
RequiredOneOf validates that at least one of the provided values is not empty. Returns nil if at least one is valid, or an InvalidArgument error listing the field names.
func RequiredPath ¶
RequiredPath validates that a path string is not empty. Returns nil if valid, or an InvalidArgument error.
func RequiredString ¶
RequiredString validates that a string field is not empty. Returns nil if valid, or an InvalidArgument error with the field name.
func RequiredURL ¶
RequiredURL validates that a URL string is not empty and is a valid URL. Returns nil if valid, or an InvalidArgument error.
func WriteServerInfo ¶
WriteServerInfo writes server information to the local data directory
Types ¶
type IdleTracker ¶
type IdleTracker struct {
// contains filtered or unexported fields
}
IdleTracker tracks server activity and determines when the server is idle.
func NewIdleTracker ¶
func NewIdleTracker(timeout time.Duration) *IdleTracker
NewIdleTracker creates a new idle tracker with the specified timeout. If timeout is 0, the tracker is disabled (never triggers shutdown).
func (*IdleTracker) IdleTimeout ¶
func (t *IdleTracker) IdleTimeout() time.Duration
IdleTimeout returns the configured idle timeout.
func (*IdleTracker) IsEnabled ¶
func (t *IdleTracker) IsEnabled() bool
IsEnabled returns true if idle timeout is enabled.
func (*IdleTracker) ShutdownChan ¶
func (t *IdleTracker) ShutdownChan() <-chan struct{}
ShutdownChan returns a channel that will be closed when idle timeout is reached.
func (*IdleTracker) Start ¶
func (t *IdleTracker) Start()
Start begins monitoring for idle timeout. This should be called in a goroutine.
type RotationScheduler ¶
type RotationScheduler struct {
// contains filtered or unexported fields
}
RotationScheduler periodically checks and auto-rotates expired encryption keys.
func NewRotationScheduler ¶
func NewRotationScheduler(db store.Store, checkInterval, maxAge time.Duration) *RotationScheduler
NewRotationScheduler creates a new rotation scheduler. checkInterval is how often to check for expired keys. maxAge is the maximum age before a key is rotated (0 to disable).
func (*RotationScheduler) Start ¶
func (rs *RotationScheduler) Start()
Start begins the rotation scheduler background task.
func (*RotationScheduler) Stop ¶
func (rs *RotationScheduler) Stop()
Stop gracefully stops the rotation scheduler.
type ServerInfo ¶
type ServerInfo struct {
Address string `json:"address"`
Port int `json:"port"`
PID int `json:"pid"`
StartedAt time.Time `json:"started_at"`
}
ServerInfo contains information about a running server
func IsServerRunning ¶
func IsServerRunning() *ServerInfo
IsServerRunning checks if a clonr server is already running. Returns the server info if running, nil otherwise. Uses goprocess to verify it's actually a running clonr process.
func ReadServerInfo ¶
func ReadServerInfo() (*ServerInfo, error)
ReadServerInfo reads the server info file if it exists
type ServerWithHealth ¶
type ServerWithHealth struct {
GRPCServer *grpc.Server
HealthServer *health.Server
IdleTracker *IdleTracker
}
ServerWithHealth wraps gRPC server and health service for lifecycle management
type Service ¶
type Service struct {
v1.UnimplementedClonrServiceServer
// contains filtered or unexported fields
}
Service implements the ClonrServiceServer interface
func NewService ¶
NewService creates a new gRPC service instance
func (*Service) DeleteProfile ¶
func (s *Service) DeleteProfile(_ context.Context, req *v1.DeleteProfileRequest) (*v1.DeleteProfileResponse, error)
DeleteProfile removes a profile by name
func (*Service) DeleteWorkspace ¶
func (s *Service) DeleteWorkspace(_ context.Context, req *v1.DeleteWorkspaceRequest) (*v1.DeleteWorkspaceResponse, error)
DeleteWorkspace removes a workspace by name
func (*Service) GetActiveProfile ¶
func (s *Service) GetActiveProfile(_ context.Context, _ *v1.GetActiveProfileRequest) (*v1.GetActiveProfileResponse, error)
GetActiveProfile retrieves the currently active profile
func (*Service) GetActiveWorkspace ¶
func (s *Service) GetActiveWorkspace(_ context.Context, _ *v1.GetActiveWorkspaceRequest) (*v1.GetActiveWorkspaceResponse, error)
GetActiveWorkspace retrieves the currently active workspace
func (*Service) GetAllRepos ¶
func (s *Service) GetAllRepos(_ context.Context, _ *v1.GetAllReposRequest) (*v1.GetAllReposResponse, error)
GetAllRepos retrieves all repositories
func (*Service) GetConfig ¶
func (s *Service) GetConfig(_ context.Context, _ *v1.GetConfigRequest) (*v1.GetConfigResponse, error)
GetConfig retrieves the application configuration
func (*Service) GetProfile ¶
func (s *Service) GetProfile(_ context.Context, req *v1.GetProfileRequest) (*v1.GetProfileResponse, error)
GetProfile retrieves a profile by name
func (*Service) GetRepos ¶
func (s *Service) GetRepos(_ context.Context, req *v1.GetReposRequest) (*v1.GetReposResponse, error)
GetRepos retrieves repositories with optional filtering
func (*Service) GetReposByWorkspace ¶
func (s *Service) GetReposByWorkspace(_ context.Context, req *v1.GetReposByWorkspaceRequest) (*v1.GetReposByWorkspaceResponse, error)
GetReposByWorkspace retrieves all repository URLs in a workspace
func (*Service) GetWorkspace ¶
func (s *Service) GetWorkspace(_ context.Context, req *v1.GetWorkspaceRequest) (*v1.GetWorkspaceResponse, error)
GetWorkspace retrieves a workspace by name
func (*Service) InsertRepoIfNotExists ¶
func (s *Service) InsertRepoIfNotExists(_ context.Context, req *v1.InsertRepoIfNotExistsRequest) (*v1.InsertRepoIfNotExistsResponse, error)
InsertRepoIfNotExists inserts a repository if it doesn't already exist
func (*Service) ListProfiles ¶
func (s *Service) ListProfiles(_ context.Context, _ *v1.ListProfilesRequest) (*v1.ListProfilesResponse, error)
ListProfiles retrieves all profiles
func (*Service) ListWorkspaces ¶
func (s *Service) ListWorkspaces(_ context.Context, _ *v1.ListWorkspacesRequest) (*v1.ListWorkspacesResponse, error)
ListWorkspaces retrieves all workspaces
func (*Service) ProfileExists ¶
func (s *Service) ProfileExists(_ context.Context, req *v1.ProfileExistsRequest) (*v1.ProfileExistsResponse, error)
ProfileExists checks if a profile exists by name
func (*Service) RemoveRepoByURL ¶
func (s *Service) RemoveRepoByURL(_ context.Context, req *v1.RemoveRepoByURLRequest) (*v1.RemoveRepoByURLResponse, error)
RemoveRepoByURL removes a repository by URL
func (*Service) RepoExistsByPath ¶
func (s *Service) RepoExistsByPath(_ context.Context, req *v1.RepoExistsByPathRequest) (*v1.RepoExistsByPathResponse, error)
RepoExistsByPath checks if a repository exists by path
func (*Service) RepoExistsByURL ¶
func (s *Service) RepoExistsByURL(_ context.Context, req *v1.RepoExistsByURLRequest) (*v1.RepoExistsByURLResponse, error)
RepoExistsByURL checks if a repository exists by URL
func (*Service) SaveConfig ¶
func (s *Service) SaveConfig(_ context.Context, req *v1.SaveConfigRequest) (*v1.SaveConfigResponse, error)
SaveConfig saves the application configuration
func (*Service) SaveProfile ¶
func (s *Service) SaveProfile(_ context.Context, req *v1.SaveProfileRequest) (*v1.SaveProfileResponse, error)
SaveProfile saves or updates a profile
func (*Service) SaveRepo ¶
func (s *Service) SaveRepo(_ context.Context, req *v1.SaveRepoRequest) (*v1.SaveRepoResponse, error)
SaveRepo saves a repository to the database
func (*Service) SaveWorkspace ¶
func (s *Service) SaveWorkspace(_ context.Context, req *v1.SaveWorkspaceRequest) (*v1.SaveWorkspaceResponse, error)
SaveWorkspace saves or updates a workspace
func (*Service) SetActiveProfile ¶
func (s *Service) SetActiveProfile(_ context.Context, req *v1.SetActiveProfileRequest) (*v1.SetActiveProfileResponse, error)
SetActiveProfile sets the active profile by name
func (*Service) SetActiveWorkspace ¶
func (s *Service) SetActiveWorkspace(_ context.Context, req *v1.SetActiveWorkspaceRequest) (*v1.SetActiveWorkspaceResponse, error)
SetActiveWorkspace sets the active workspace by name
func (*Service) SetFavoriteByURL ¶
func (s *Service) SetFavoriteByURL(_ context.Context, req *v1.SetFavoriteRequest) (*v1.SetFavoriteResponse, error)
SetFavoriteByURL marks or unmarks a repository as favorite
func (*Service) UpdateRepoTimestamp ¶
func (s *Service) UpdateRepoTimestamp(_ context.Context, req *v1.UpdateRepoTimestampRequest) (*v1.UpdateRepoTimestampResponse, error)
UpdateRepoTimestamp updates the timestamp for a repository
func (*Service) UpdateRepoWorkspace ¶
func (s *Service) UpdateRepoWorkspace(_ context.Context, req *v1.UpdateRepoWorkspaceRequest) (*v1.UpdateRepoWorkspaceResponse, error)
UpdateRepoWorkspace updates the workspace for a repository
func (*Service) WorkspaceExists ¶
func (s *Service) WorkspaceExists(_ context.Context, req *v1.WorkspaceExistsRequest) (*v1.WorkspaceExistsResponse, error)
WorkspaceExists checks if a workspace exists by name