feat: SFTP configuration UI (enabled for sftp: URIs) with support for SSH key bootstrapping (#961)
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled

Co-authored-by: Gareth <garethgeorge97@gmail.com>
This commit is contained in:
Kirari04
2026-01-27 08:27:33 +01:00
committed by GitHub
parent 43577232d5
commit 3250ff481d
13 changed files with 2257 additions and 597 deletions
+466 -133
View File
@@ -79,7 +79,7 @@ func (x DoRepoTaskRequest_Task) Number() protoreflect.EnumNumber {
// Deprecated: Use DoRepoTaskRequest_Task.Descriptor instead.
func (DoRepoTaskRequest_Task) EnumDescriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{1, 0}
return file_v1_service_proto_rawDescGZIP(), []int{6, 0}
}
// OpSelector is a message that can be used to select operations e.g. by query.
@@ -183,6 +183,306 @@ func (x *OpSelector) GetModnoGte() int64 {
return 0
}
type SetupSftpRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
Port string `protobuf:"bytes,2,opt,name=port,proto3" json:"port,omitempty"`
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
Password *string `protobuf:"bytes,4,opt,name=password,proto3,oneof" json:"password,omitempty"` // If not provided, we only generate the key and add host to known_hosts
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetupSftpRequest) Reset() {
*x = SetupSftpRequest{}
mi := &file_v1_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetupSftpRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetupSftpRequest) ProtoMessage() {}
func (x *SetupSftpRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetupSftpRequest.ProtoReflect.Descriptor instead.
func (*SetupSftpRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{1}
}
func (x *SetupSftpRequest) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
func (x *SetupSftpRequest) GetPort() string {
if x != nil {
return x.Port
}
return ""
}
func (x *SetupSftpRequest) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *SetupSftpRequest) GetPassword() string {
if x != nil && x.Password != nil {
return *x.Password
}
return ""
}
type SetupSftpResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
KeyPath string `protobuf:"bytes,2,opt,name=key_path,json=keyPath,proto3" json:"key_path,omitempty"`
KnownHostsPath string `protobuf:"bytes,3,opt,name=known_hosts_path,json=knownHostsPath,proto3" json:"known_hosts_path,omitempty"`
Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetupSftpResponse) Reset() {
*x = SetupSftpResponse{}
mi := &file_v1_service_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetupSftpResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetupSftpResponse) ProtoMessage() {}
func (x *SetupSftpResponse) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetupSftpResponse.ProtoReflect.Descriptor instead.
func (*SetupSftpResponse) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{2}
}
func (x *SetupSftpResponse) GetPublicKey() string {
if x != nil {
return x.PublicKey
}
return ""
}
func (x *SetupSftpResponse) GetKeyPath() string {
if x != nil {
return x.KeyPath
}
return ""
}
func (x *SetupSftpResponse) GetKnownHostsPath() string {
if x != nil {
return x.KnownHostsPath
}
return ""
}
func (x *SetupSftpResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type CheckRepoExistsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Repo *Repo `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
TrustSftpHostKey bool `protobuf:"varint,2,opt,name=trust_sftp_host_key,json=trustSftpHostKey,proto3" json:"trust_sftp_host_key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CheckRepoExistsRequest) Reset() {
*x = CheckRepoExistsRequest{}
mi := &file_v1_service_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckRepoExistsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckRepoExistsRequest) ProtoMessage() {}
func (x *CheckRepoExistsRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CheckRepoExistsRequest.ProtoReflect.Descriptor instead.
func (*CheckRepoExistsRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{3}
}
func (x *CheckRepoExistsRequest) GetRepo() *Repo {
if x != nil {
return x.Repo
}
return nil
}
func (x *CheckRepoExistsRequest) GetTrustSftpHostKey() bool {
if x != nil {
return x.TrustSftpHostKey
}
return false
}
type CheckRepoExistsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
HostKeyUntrusted bool `protobuf:"varint,5,opt,name=host_key_untrusted,json=hostKeyUntrusted,proto3" json:"host_key_untrusted,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CheckRepoExistsResponse) Reset() {
*x = CheckRepoExistsResponse{}
mi := &file_v1_service_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckRepoExistsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckRepoExistsResponse) ProtoMessage() {}
func (x *CheckRepoExistsResponse) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CheckRepoExistsResponse.ProtoReflect.Descriptor instead.
func (*CheckRepoExistsResponse) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{4}
}
func (x *CheckRepoExistsResponse) GetExists() bool {
if x != nil {
return x.Exists
}
return false
}
func (x *CheckRepoExistsResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
func (x *CheckRepoExistsResponse) GetHostKeyUntrusted() bool {
if x != nil {
return x.HostKeyUntrusted
}
return false
}
type AddRepoRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Repo *Repo `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
TrustSftpHostKey bool `protobuf:"varint,2,opt,name=trust_sftp_host_key,json=trustSftpHostKey,proto3" json:"trust_sftp_host_key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AddRepoRequest) Reset() {
*x = AddRepoRequest{}
mi := &file_v1_service_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AddRepoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddRepoRequest) ProtoMessage() {}
func (x *AddRepoRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddRepoRequest.ProtoReflect.Descriptor instead.
func (*AddRepoRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{5}
}
func (x *AddRepoRequest) GetRepo() *Repo {
if x != nil {
return x.Repo
}
return nil
}
func (x *AddRepoRequest) GetTrustSftpHostKey() bool {
if x != nil {
return x.TrustSftpHostKey
}
return false
}
type DoRepoTaskRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
RepoId string `protobuf:"bytes,1,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"`
@@ -193,7 +493,7 @@ type DoRepoTaskRequest struct {
func (x *DoRepoTaskRequest) Reset() {
*x = DoRepoTaskRequest{}
mi := &file_v1_service_proto_msgTypes[1]
mi := &file_v1_service_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -205,7 +505,7 @@ func (x *DoRepoTaskRequest) String() string {
func (*DoRepoTaskRequest) ProtoMessage() {}
func (x *DoRepoTaskRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[1]
mi := &file_v1_service_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -218,7 +518,7 @@ func (x *DoRepoTaskRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DoRepoTaskRequest.ProtoReflect.Descriptor instead.
func (*DoRepoTaskRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{1}
return file_v1_service_proto_rawDescGZIP(), []int{6}
}
func (x *DoRepoTaskRequest) GetRepoId() string {
@@ -245,7 +545,7 @@ type ClearHistoryRequest struct {
func (x *ClearHistoryRequest) Reset() {
*x = ClearHistoryRequest{}
mi := &file_v1_service_proto_msgTypes[2]
mi := &file_v1_service_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -257,7 +557,7 @@ func (x *ClearHistoryRequest) String() string {
func (*ClearHistoryRequest) ProtoMessage() {}
func (x *ClearHistoryRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[2]
mi := &file_v1_service_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -270,7 +570,7 @@ func (x *ClearHistoryRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClearHistoryRequest.ProtoReflect.Descriptor instead.
func (*ClearHistoryRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{2}
return file_v1_service_proto_rawDescGZIP(), []int{7}
}
func (x *ClearHistoryRequest) GetSelector() *OpSelector {
@@ -298,7 +598,7 @@ type ForgetRequest struct {
func (x *ForgetRequest) Reset() {
*x = ForgetRequest{}
mi := &file_v1_service_proto_msgTypes[3]
mi := &file_v1_service_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -310,7 +610,7 @@ func (x *ForgetRequest) String() string {
func (*ForgetRequest) ProtoMessage() {}
func (x *ForgetRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[3]
mi := &file_v1_service_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -323,7 +623,7 @@ func (x *ForgetRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ForgetRequest.ProtoReflect.Descriptor instead.
func (*ForgetRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{3}
return file_v1_service_proto_rawDescGZIP(), []int{8}
}
func (x *ForgetRequest) GetRepoId() string {
@@ -357,7 +657,7 @@ type ListSnapshotsRequest struct {
func (x *ListSnapshotsRequest) Reset() {
*x = ListSnapshotsRequest{}
mi := &file_v1_service_proto_msgTypes[4]
mi := &file_v1_service_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -369,7 +669,7 @@ func (x *ListSnapshotsRequest) String() string {
func (*ListSnapshotsRequest) ProtoMessage() {}
func (x *ListSnapshotsRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[4]
mi := &file_v1_service_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -382,7 +682,7 @@ func (x *ListSnapshotsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListSnapshotsRequest.ProtoReflect.Descriptor instead.
func (*ListSnapshotsRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{4}
return file_v1_service_proto_rawDescGZIP(), []int{9}
}
func (x *ListSnapshotsRequest) GetRepoId() string {
@@ -409,7 +709,7 @@ type GetOperationsRequest struct {
func (x *GetOperationsRequest) Reset() {
*x = GetOperationsRequest{}
mi := &file_v1_service_proto_msgTypes[5]
mi := &file_v1_service_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -421,7 +721,7 @@ func (x *GetOperationsRequest) String() string {
func (*GetOperationsRequest) ProtoMessage() {}
func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[5]
mi := &file_v1_service_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -434,7 +734,7 @@ func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetOperationsRequest.ProtoReflect.Descriptor instead.
func (*GetOperationsRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{5}
return file_v1_service_proto_rawDescGZIP(), []int{10}
}
func (x *GetOperationsRequest) GetSelector() *OpSelector {
@@ -464,7 +764,7 @@ type RestoreSnapshotRequest struct {
func (x *RestoreSnapshotRequest) Reset() {
*x = RestoreSnapshotRequest{}
mi := &file_v1_service_proto_msgTypes[6]
mi := &file_v1_service_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -476,7 +776,7 @@ func (x *RestoreSnapshotRequest) String() string {
func (*RestoreSnapshotRequest) ProtoMessage() {}
func (x *RestoreSnapshotRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[6]
mi := &file_v1_service_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -489,7 +789,7 @@ func (x *RestoreSnapshotRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RestoreSnapshotRequest.ProtoReflect.Descriptor instead.
func (*RestoreSnapshotRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{6}
return file_v1_service_proto_rawDescGZIP(), []int{11}
}
func (x *RestoreSnapshotRequest) GetPlanId() string {
@@ -538,7 +838,7 @@ type ListSnapshotFilesRequest struct {
func (x *ListSnapshotFilesRequest) Reset() {
*x = ListSnapshotFilesRequest{}
mi := &file_v1_service_proto_msgTypes[7]
mi := &file_v1_service_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -550,7 +850,7 @@ func (x *ListSnapshotFilesRequest) String() string {
func (*ListSnapshotFilesRequest) ProtoMessage() {}
func (x *ListSnapshotFilesRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[7]
mi := &file_v1_service_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -563,7 +863,7 @@ func (x *ListSnapshotFilesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListSnapshotFilesRequest.ProtoReflect.Descriptor instead.
func (*ListSnapshotFilesRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{7}
return file_v1_service_proto_rawDescGZIP(), []int{12}
}
func (x *ListSnapshotFilesRequest) GetRepoGuid() string {
@@ -597,7 +897,7 @@ type ListSnapshotFilesResponse struct {
func (x *ListSnapshotFilesResponse) Reset() {
*x = ListSnapshotFilesResponse{}
mi := &file_v1_service_proto_msgTypes[8]
mi := &file_v1_service_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -609,7 +909,7 @@ func (x *ListSnapshotFilesResponse) String() string {
func (*ListSnapshotFilesResponse) ProtoMessage() {}
func (x *ListSnapshotFilesResponse) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[8]
mi := &file_v1_service_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -622,7 +922,7 @@ func (x *ListSnapshotFilesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListSnapshotFilesResponse.ProtoReflect.Descriptor instead.
func (*ListSnapshotFilesResponse) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{8}
return file_v1_service_proto_rawDescGZIP(), []int{13}
}
func (x *ListSnapshotFilesResponse) GetPath() string {
@@ -648,7 +948,7 @@ type LogDataRequest struct {
func (x *LogDataRequest) Reset() {
*x = LogDataRequest{}
mi := &file_v1_service_proto_msgTypes[9]
mi := &file_v1_service_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -660,7 +960,7 @@ func (x *LogDataRequest) String() string {
func (*LogDataRequest) ProtoMessage() {}
func (x *LogDataRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[9]
mi := &file_v1_service_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -673,7 +973,7 @@ func (x *LogDataRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogDataRequest.ProtoReflect.Descriptor instead.
func (*LogDataRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{9}
return file_v1_service_proto_rawDescGZIP(), []int{14}
}
func (x *LogDataRequest) GetRef() string {
@@ -693,7 +993,7 @@ type GetDownloadURLRequest struct {
func (x *GetDownloadURLRequest) Reset() {
*x = GetDownloadURLRequest{}
mi := &file_v1_service_proto_msgTypes[10]
mi := &file_v1_service_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -705,7 +1005,7 @@ func (x *GetDownloadURLRequest) String() string {
func (*GetDownloadURLRequest) ProtoMessage() {}
func (x *GetDownloadURLRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[10]
mi := &file_v1_service_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -718,7 +1018,7 @@ func (x *GetDownloadURLRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetDownloadURLRequest.ProtoReflect.Descriptor instead.
func (*GetDownloadURLRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{10}
return file_v1_service_proto_rawDescGZIP(), []int{15}
}
func (x *GetDownloadURLRequest) GetOpId() int64 {
@@ -753,7 +1053,7 @@ type LsEntry struct {
func (x *LsEntry) Reset() {
*x = LsEntry{}
mi := &file_v1_service_proto_msgTypes[11]
mi := &file_v1_service_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -765,7 +1065,7 @@ func (x *LsEntry) String() string {
func (*LsEntry) ProtoMessage() {}
func (x *LsEntry) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[11]
mi := &file_v1_service_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -778,7 +1078,7 @@ func (x *LsEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use LsEntry.ProtoReflect.Descriptor instead.
func (*LsEntry) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{11}
return file_v1_service_proto_rawDescGZIP(), []int{16}
}
func (x *LsEntry) GetName() string {
@@ -861,7 +1161,7 @@ type RunCommandRequest struct {
func (x *RunCommandRequest) Reset() {
*x = RunCommandRequest{}
mi := &file_v1_service_proto_msgTypes[12]
mi := &file_v1_service_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -873,7 +1173,7 @@ func (x *RunCommandRequest) String() string {
func (*RunCommandRequest) ProtoMessage() {}
func (x *RunCommandRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[12]
mi := &file_v1_service_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -886,7 +1186,7 @@ func (x *RunCommandRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RunCommandRequest.ProtoReflect.Descriptor instead.
func (*RunCommandRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{12}
return file_v1_service_proto_rawDescGZIP(), []int{17}
}
func (x *RunCommandRequest) GetRepoId() string {
@@ -915,7 +1215,7 @@ type SummaryDashboardResponse struct {
func (x *SummaryDashboardResponse) Reset() {
*x = SummaryDashboardResponse{}
mi := &file_v1_service_proto_msgTypes[13]
mi := &file_v1_service_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -927,7 +1227,7 @@ func (x *SummaryDashboardResponse) String() string {
func (*SummaryDashboardResponse) ProtoMessage() {}
func (x *SummaryDashboardResponse) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[13]
mi := &file_v1_service_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -940,7 +1240,7 @@ func (x *SummaryDashboardResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SummaryDashboardResponse.ProtoReflect.Descriptor instead.
func (*SummaryDashboardResponse) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{13}
return file_v1_service_proto_rawDescGZIP(), []int{18}
}
func (x *SummaryDashboardResponse) GetRepoSummaries() []*SummaryDashboardResponse_Summary {
@@ -991,7 +1291,7 @@ type SummaryDashboardResponse_Summary struct {
func (x *SummaryDashboardResponse_Summary) Reset() {
*x = SummaryDashboardResponse_Summary{}
mi := &file_v1_service_proto_msgTypes[14]
mi := &file_v1_service_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1003,7 +1303,7 @@ func (x *SummaryDashboardResponse_Summary) String() string {
func (*SummaryDashboardResponse_Summary) ProtoMessage() {}
func (x *SummaryDashboardResponse_Summary) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[14]
mi := &file_v1_service_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1016,7 +1316,7 @@ func (x *SummaryDashboardResponse_Summary) ProtoReflect() protoreflect.Message {
// Deprecated: Use SummaryDashboardResponse_Summary.ProtoReflect.Descriptor instead.
func (*SummaryDashboardResponse_Summary) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{13, 0}
return file_v1_service_proto_rawDescGZIP(), []int{18, 0}
}
func (x *SummaryDashboardResponse_Summary) GetId() string {
@@ -1109,7 +1409,7 @@ type SummaryDashboardResponse_BackupChart struct {
func (x *SummaryDashboardResponse_BackupChart) Reset() {
*x = SummaryDashboardResponse_BackupChart{}
mi := &file_v1_service_proto_msgTypes[15]
mi := &file_v1_service_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1121,7 +1421,7 @@ func (x *SummaryDashboardResponse_BackupChart) String() string {
func (*SummaryDashboardResponse_BackupChart) ProtoMessage() {}
func (x *SummaryDashboardResponse_BackupChart) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[15]
mi := &file_v1_service_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1134,7 +1434,7 @@ func (x *SummaryDashboardResponse_BackupChart) ProtoReflect() protoreflect.Messa
// Deprecated: Use SummaryDashboardResponse_BackupChart.ProtoReflect.Descriptor instead.
func (*SummaryDashboardResponse_BackupChart) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{13, 1}
return file_v1_service_proto_rawDescGZIP(), []int{18, 1}
}
func (x *SummaryDashboardResponse_BackupChart) GetFlowId() []int64 {
@@ -1199,7 +1499,29 @@ const file_v1_service_proto_rawDesc = "" +
"\n" +
"\b_flow_idB\f\n" +
"\n" +
"_modno_gte\"\xce\x01\n" +
"_modno_gte\"\x84\x01\n" +
"\x10SetupSftpRequest\x12\x12\n" +
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
"\x04port\x18\x02 \x01(\tR\x04port\x12\x1a\n" +
"\busername\x18\x03 \x01(\tR\busername\x12\x1f\n" +
"\bpassword\x18\x04 \x01(\tH\x00R\bpassword\x88\x01\x01B\v\n" +
"\t_password\"\x8d\x01\n" +
"\x11SetupSftpResponse\x12\x1d\n" +
"\n" +
"public_key\x18\x01 \x01(\tR\tpublicKey\x12\x19\n" +
"\bkey_path\x18\x02 \x01(\tR\akeyPath\x12(\n" +
"\x10known_hosts_path\x18\x03 \x01(\tR\x0eknownHostsPath\x12\x14\n" +
"\x05error\x18\x04 \x01(\tR\x05error\"e\n" +
"\x16CheckRepoExistsRequest\x12\x1c\n" +
"\x04repo\x18\x01 \x01(\v2\b.v1.RepoR\x04repo\x12-\n" +
"\x13trust_sftp_host_key\x18\x02 \x01(\bR\x10trustSftpHostKey\"u\n" +
"\x17CheckRepoExistsResponse\x12\x16\n" +
"\x06exists\x18\x01 \x01(\bR\x06exists\x12\x14\n" +
"\x05error\x18\x02 \x01(\tR\x05error\x12,\n" +
"\x12host_key_untrusted\x18\x05 \x01(\bR\x10hostKeyUntrusted\"]\n" +
"\x0eAddRepoRequest\x12\x1c\n" +
"\x04repo\x18\x01 \x01(\v2\b.v1.RepoR\x04repo\x12-\n" +
"\x13trust_sftp_host_key\x18\x02 \x01(\bR\x10trustSftpHostKey\"\xce\x01\n" +
"\x11DoRepoTaskRequest\x12\x17\n" +
"\arepo_id\x18\x01 \x01(\tR\x06repoId\x12.\n" +
"\x04task\x18\x02 \x01(\x0e2\x1a.v1.DoRepoTaskRequest.TaskR\x04task\"p\n" +
@@ -1290,15 +1612,17 @@ const file_v1_service_proto_rawDesc = "" +
"durationMs\x12+\n" +
"\x06status\x18\x04 \x03(\x0e2\x13.v1.OperationStatusR\x06status\x12\x1f\n" +
"\vbytes_added\x18\x05 \x03(\x03R\n" +
"bytesAdded2\xaf\t\n" +
"bytesAdded2\x92\n" +
"\n" +
"\bBackrest\x121\n" +
"\tGetConfig\x12\x16.google.protobuf.Empty\x1a\n" +
".v1.Config\"\x00\x12%\n" +
"\tSetConfig\x12\n" +
".v1.Config\x1a\n" +
".v1.Config\"\x00\x12/\n" +
"\x0fCheckRepoExists\x12\b.v1.Repo\x1a\x10.types.BoolValue\"\x00\x12!\n" +
"\aAddRepo\x12\b.v1.Repo\x1a\n" +
".v1.Config\"\x00\x12:\n" +
"\tSetupSftp\x12\x14.v1.SetupSftpRequest\x1a\x15.v1.SetupSftpResponse\"\x00\x12L\n" +
"\x0fCheckRepoExists\x12\x1a.v1.CheckRepoExistsRequest\x1a\x1b.v1.CheckRepoExistsResponse\"\x00\x12+\n" +
"\aAddRepo\x12\x12.v1.AddRepoRequest\x1a\n" +
".v1.Config\"\x00\x12.\n" +
"\n" +
"RemoveRepo\x12\x12.types.StringValue\x1a\n" +
@@ -1334,92 +1658,100 @@ func file_v1_service_proto_rawDescGZIP() []byte {
}
var file_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
var file_v1_service_proto_goTypes = []any{
(DoRepoTaskRequest_Task)(0), // 0: v1.DoRepoTaskRequest.Task
(*OpSelector)(nil), // 1: v1.OpSelector
(*DoRepoTaskRequest)(nil), // 2: v1.DoRepoTaskRequest
(*ClearHistoryRequest)(nil), // 3: v1.ClearHistoryRequest
(*ForgetRequest)(nil), // 4: v1.ForgetRequest
(*ListSnapshotsRequest)(nil), // 5: v1.ListSnapshotsRequest
(*GetOperationsRequest)(nil), // 6: v1.GetOperationsRequest
(*RestoreSnapshotRequest)(nil), // 7: v1.RestoreSnapshotRequest
(*ListSnapshotFilesRequest)(nil), // 8: v1.ListSnapshotFilesRequest
(*ListSnapshotFilesResponse)(nil), // 9: v1.ListSnapshotFilesResponse
(*LogDataRequest)(nil), // 10: v1.LogDataRequest
(*GetDownloadURLRequest)(nil), // 11: v1.GetDownloadURLRequest
(*LsEntry)(nil), // 12: v1.LsEntry
(*RunCommandRequest)(nil), // 13: v1.RunCommandRequest
(*SummaryDashboardResponse)(nil), // 14: v1.SummaryDashboardResponse
(*SummaryDashboardResponse_Summary)(nil), // 15: v1.SummaryDashboardResponse.Summary
(*SummaryDashboardResponse_BackupChart)(nil), // 16: v1.SummaryDashboardResponse.BackupChart
(OperationStatus)(0), // 17: v1.OperationStatus
(*emptypb.Empty)(nil), // 18: google.protobuf.Empty
(*Config)(nil), // 19: v1.Config
(*Repo)(nil), // 20: v1.Repo
(*types.StringValue)(nil), // 21: types.StringValue
(*types.Int64Value)(nil), // 22: types.Int64Value
(*types.BoolValue)(nil), // 23: types.BoolValue
(*OperationEvent)(nil), // 24: v1.OperationEvent
(*OperationList)(nil), // 25: v1.OperationList
(*ResticSnapshotList)(nil), // 26: v1.ResticSnapshotList
(*types.BytesValue)(nil), // 27: types.BytesValue
(*types.StringList)(nil), // 28: types.StringList
(*SetupSftpRequest)(nil), // 2: v1.SetupSftpRequest
(*SetupSftpResponse)(nil), // 3: v1.SetupSftpResponse
(*CheckRepoExistsRequest)(nil), // 4: v1.CheckRepoExistsRequest
(*CheckRepoExistsResponse)(nil), // 5: v1.CheckRepoExistsResponse
(*AddRepoRequest)(nil), // 6: v1.AddRepoRequest
(*DoRepoTaskRequest)(nil), // 7: v1.DoRepoTaskRequest
(*ClearHistoryRequest)(nil), // 8: v1.ClearHistoryRequest
(*ForgetRequest)(nil), // 9: v1.ForgetRequest
(*ListSnapshotsRequest)(nil), // 10: v1.ListSnapshotsRequest
(*GetOperationsRequest)(nil), // 11: v1.GetOperationsRequest
(*RestoreSnapshotRequest)(nil), // 12: v1.RestoreSnapshotRequest
(*ListSnapshotFilesRequest)(nil), // 13: v1.ListSnapshotFilesRequest
(*ListSnapshotFilesResponse)(nil), // 14: v1.ListSnapshotFilesResponse
(*LogDataRequest)(nil), // 15: v1.LogDataRequest
(*GetDownloadURLRequest)(nil), // 16: v1.GetDownloadURLRequest
(*LsEntry)(nil), // 17: v1.LsEntry
(*RunCommandRequest)(nil), // 18: v1.RunCommandRequest
(*SummaryDashboardResponse)(nil), // 19: v1.SummaryDashboardResponse
(*SummaryDashboardResponse_Summary)(nil), // 20: v1.SummaryDashboardResponse.Summary
(*SummaryDashboardResponse_BackupChart)(nil), // 21: v1.SummaryDashboardResponse.BackupChart
(*Repo)(nil), // 22: v1.Repo
(OperationStatus)(0), // 23: v1.OperationStatus
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty
(*Config)(nil), // 25: v1.Config
(*types.StringValue)(nil), // 26: types.StringValue
(*types.Int64Value)(nil), // 27: types.Int64Value
(*OperationEvent)(nil), // 28: v1.OperationEvent
(*OperationList)(nil), // 29: v1.OperationList
(*ResticSnapshotList)(nil), // 30: v1.ResticSnapshotList
(*types.BytesValue)(nil), // 31: types.BytesValue
(*types.StringList)(nil), // 32: types.StringList
}
var file_v1_service_proto_depIdxs = []int32{
0, // 0: v1.DoRepoTaskRequest.task:type_name -> v1.DoRepoTaskRequest.Task
1, // 1: v1.ClearHistoryRequest.selector:type_name -> v1.OpSelector
1, // 2: v1.GetOperationsRequest.selector:type_name -> v1.OpSelector
12, // 3: v1.ListSnapshotFilesResponse.entries:type_name -> v1.LsEntry
15, // 4: v1.SummaryDashboardResponse.repo_summaries:type_name -> v1.SummaryDashboardResponse.Summary
15, // 5: v1.SummaryDashboardResponse.plan_summaries:type_name -> v1.SummaryDashboardResponse.Summary
16, // 6: v1.SummaryDashboardResponse.Summary.recent_backups:type_name -> v1.SummaryDashboardResponse.BackupChart
17, // 7: v1.SummaryDashboardResponse.BackupChart.status:type_name -> v1.OperationStatus
18, // 8: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty
19, // 9: v1.Backrest.SetConfig:input_type -> v1.Config
20, // 10: v1.Backrest.CheckRepoExists:input_type -> v1.Repo
20, // 11: v1.Backrest.AddRepo:input_type -> v1.Repo
21, // 12: v1.Backrest.RemoveRepo:input_type -> types.StringValue
18, // 13: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty
6, // 14: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest
5, // 15: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest
8, // 16: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest
21, // 17: v1.Backrest.Backup:input_type -> types.StringValue
2, // 18: v1.Backrest.DoRepoTask:input_type -> v1.DoRepoTaskRequest
4, // 19: v1.Backrest.Forget:input_type -> v1.ForgetRequest
7, // 20: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest
22, // 21: v1.Backrest.Cancel:input_type -> types.Int64Value
10, // 22: v1.Backrest.GetLogs:input_type -> v1.LogDataRequest
13, // 23: v1.Backrest.RunCommand:input_type -> v1.RunCommandRequest
11, // 24: v1.Backrest.GetDownloadURL:input_type -> v1.GetDownloadURLRequest
3, // 25: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest
21, // 26: v1.Backrest.PathAutocomplete:input_type -> types.StringValue
18, // 27: v1.Backrest.GetSummaryDashboard:input_type -> google.protobuf.Empty
19, // 28: v1.Backrest.GetConfig:output_type -> v1.Config
19, // 29: v1.Backrest.SetConfig:output_type -> v1.Config
23, // 30: v1.Backrest.CheckRepoExists:output_type -> types.BoolValue
19, // 31: v1.Backrest.AddRepo:output_type -> v1.Config
19, // 32: v1.Backrest.RemoveRepo:output_type -> v1.Config
24, // 33: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent
25, // 34: v1.Backrest.GetOperations:output_type -> v1.OperationList
26, // 35: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList
9, // 36: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse
18, // 37: v1.Backrest.Backup:output_type -> google.protobuf.Empty
18, // 38: v1.Backrest.DoRepoTask:output_type -> google.protobuf.Empty
18, // 39: v1.Backrest.Forget:output_type -> google.protobuf.Empty
18, // 40: v1.Backrest.Restore:output_type -> google.protobuf.Empty
18, // 41: v1.Backrest.Cancel:output_type -> google.protobuf.Empty
27, // 42: v1.Backrest.GetLogs:output_type -> types.BytesValue
22, // 43: v1.Backrest.RunCommand:output_type -> types.Int64Value
21, // 44: v1.Backrest.GetDownloadURL:output_type -> types.StringValue
18, // 45: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty
28, // 46: v1.Backrest.PathAutocomplete:output_type -> types.StringList
14, // 47: v1.Backrest.GetSummaryDashboard:output_type -> v1.SummaryDashboardResponse
28, // [28:48] is the sub-list for method output_type
8, // [8:28] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
22, // 0: v1.CheckRepoExistsRequest.repo:type_name -> v1.Repo
22, // 1: v1.AddRepoRequest.repo:type_name -> v1.Repo
0, // 2: v1.DoRepoTaskRequest.task:type_name -> v1.DoRepoTaskRequest.Task
1, // 3: v1.ClearHistoryRequest.selector:type_name -> v1.OpSelector
1, // 4: v1.GetOperationsRequest.selector:type_name -> v1.OpSelector
17, // 5: v1.ListSnapshotFilesResponse.entries:type_name -> v1.LsEntry
20, // 6: v1.SummaryDashboardResponse.repo_summaries:type_name -> v1.SummaryDashboardResponse.Summary
20, // 7: v1.SummaryDashboardResponse.plan_summaries:type_name -> v1.SummaryDashboardResponse.Summary
21, // 8: v1.SummaryDashboardResponse.Summary.recent_backups:type_name -> v1.SummaryDashboardResponse.BackupChart
23, // 9: v1.SummaryDashboardResponse.BackupChart.status:type_name -> v1.OperationStatus
24, // 10: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty
25, // 11: v1.Backrest.SetConfig:input_type -> v1.Config
2, // 12: v1.Backrest.SetupSftp:input_type -> v1.SetupSftpRequest
4, // 13: v1.Backrest.CheckRepoExists:input_type -> v1.CheckRepoExistsRequest
6, // 14: v1.Backrest.AddRepo:input_type -> v1.AddRepoRequest
26, // 15: v1.Backrest.RemoveRepo:input_type -> types.StringValue
24, // 16: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty
11, // 17: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest
10, // 18: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest
13, // 19: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest
26, // 20: v1.Backrest.Backup:input_type -> types.StringValue
7, // 21: v1.Backrest.DoRepoTask:input_type -> v1.DoRepoTaskRequest
9, // 22: v1.Backrest.Forget:input_type -> v1.ForgetRequest
12, // 23: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest
27, // 24: v1.Backrest.Cancel:input_type -> types.Int64Value
15, // 25: v1.Backrest.GetLogs:input_type -> v1.LogDataRequest
18, // 26: v1.Backrest.RunCommand:input_type -> v1.RunCommandRequest
16, // 27: v1.Backrest.GetDownloadURL:input_type -> v1.GetDownloadURLRequest
8, // 28: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest
26, // 29: v1.Backrest.PathAutocomplete:input_type -> types.StringValue
24, // 30: v1.Backrest.GetSummaryDashboard:input_type -> google.protobuf.Empty
25, // 31: v1.Backrest.GetConfig:output_type -> v1.Config
25, // 32: v1.Backrest.SetConfig:output_type -> v1.Config
3, // 33: v1.Backrest.SetupSftp:output_type -> v1.SetupSftpResponse
5, // 34: v1.Backrest.CheckRepoExists:output_type -> v1.CheckRepoExistsResponse
25, // 35: v1.Backrest.AddRepo:output_type -> v1.Config
25, // 36: v1.Backrest.RemoveRepo:output_type -> v1.Config
28, // 37: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent
29, // 38: v1.Backrest.GetOperations:output_type -> v1.OperationList
30, // 39: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList
14, // 40: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse
24, // 41: v1.Backrest.Backup:output_type -> google.protobuf.Empty
24, // 42: v1.Backrest.DoRepoTask:output_type -> google.protobuf.Empty
24, // 43: v1.Backrest.Forget:output_type -> google.protobuf.Empty
24, // 44: v1.Backrest.Restore:output_type -> google.protobuf.Empty
24, // 45: v1.Backrest.Cancel:output_type -> google.protobuf.Empty
31, // 46: v1.Backrest.GetLogs:output_type -> types.BytesValue
27, // 47: v1.Backrest.RunCommand:output_type -> types.Int64Value
26, // 48: v1.Backrest.GetDownloadURL:output_type -> types.StringValue
24, // 49: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty
32, // 50: v1.Backrest.PathAutocomplete:output_type -> types.StringList
19, // 51: v1.Backrest.GetSummaryDashboard:output_type -> v1.SummaryDashboardResponse
31, // [31:52] is the sub-list for method output_type
10, // [10:31] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_v1_service_proto_init() }
@@ -1431,13 +1763,14 @@ func file_v1_service_proto_init() {
file_v1_restic_proto_init()
file_v1_operations_proto_init()
file_v1_service_proto_msgTypes[0].OneofWrappers = []any{}
file_v1_service_proto_msgTypes[1].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_v1_service_proto_rawDesc), len(file_v1_service_proto_rawDesc)),
NumEnums: 1,
NumMessages: 16,
NumMessages: 21,
NumExtensions: 0,
NumServices: 1,
},
+51 -13
View File
@@ -23,6 +23,7 @@ const _ = grpc.SupportPackageIsVersion9
const (
Backrest_GetConfig_FullMethodName = "/v1.Backrest/GetConfig"
Backrest_SetConfig_FullMethodName = "/v1.Backrest/SetConfig"
Backrest_SetupSftp_FullMethodName = "/v1.Backrest/SetupSftp"
Backrest_CheckRepoExists_FullMethodName = "/v1.Backrest/CheckRepoExists"
Backrest_AddRepo_FullMethodName = "/v1.Backrest/AddRepo"
Backrest_RemoveRepo_FullMethodName = "/v1.Backrest/RemoveRepo"
@@ -49,8 +50,9 @@ const (
type BackrestClient interface {
GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Config, error)
SetConfig(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Config, error)
CheckRepoExists(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*types.BoolValue, error)
AddRepo(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*Config, error)
SetupSftp(ctx context.Context, in *SetupSftpRequest, opts ...grpc.CallOption) (*SetupSftpResponse, error)
CheckRepoExists(ctx context.Context, in *CheckRepoExistsRequest, opts ...grpc.CallOption) (*CheckRepoExistsResponse, error)
AddRepo(ctx context.Context, in *AddRepoRequest, opts ...grpc.CallOption) (*Config, error)
RemoveRepo(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*Config, error)
GetOperationEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OperationEvent], error)
GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*OperationList, error)
@@ -108,9 +110,19 @@ func (c *backrestClient) SetConfig(ctx context.Context, in *Config, opts ...grpc
return out, nil
}
func (c *backrestClient) CheckRepoExists(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*types.BoolValue, error) {
func (c *backrestClient) SetupSftp(ctx context.Context, in *SetupSftpRequest, opts ...grpc.CallOption) (*SetupSftpResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(types.BoolValue)
out := new(SetupSftpResponse)
err := c.cc.Invoke(ctx, Backrest_SetupSftp_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backrestClient) CheckRepoExists(ctx context.Context, in *CheckRepoExistsRequest, opts ...grpc.CallOption) (*CheckRepoExistsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CheckRepoExistsResponse)
err := c.cc.Invoke(ctx, Backrest_CheckRepoExists_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
@@ -118,7 +130,7 @@ func (c *backrestClient) CheckRepoExists(ctx context.Context, in *Repo, opts ...
return out, nil
}
func (c *backrestClient) AddRepo(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*Config, error) {
func (c *backrestClient) AddRepo(ctx context.Context, in *AddRepoRequest, opts ...grpc.CallOption) (*Config, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Config)
err := c.cc.Invoke(ctx, Backrest_AddRepo_FullMethodName, in, out, cOpts...)
@@ -312,8 +324,9 @@ func (c *backrestClient) GetSummaryDashboard(ctx context.Context, in *emptypb.Em
type BackrestServer interface {
GetConfig(context.Context, *emptypb.Empty) (*Config, error)
SetConfig(context.Context, *Config) (*Config, error)
CheckRepoExists(context.Context, *Repo) (*types.BoolValue, error)
AddRepo(context.Context, *Repo) (*Config, error)
SetupSftp(context.Context, *SetupSftpRequest) (*SetupSftpResponse, error)
CheckRepoExists(context.Context, *CheckRepoExistsRequest) (*CheckRepoExistsResponse, error)
AddRepo(context.Context, *AddRepoRequest) (*Config, error)
RemoveRepo(context.Context, *types.StringValue) (*Config, error)
GetOperationEvents(*emptypb.Empty, grpc.ServerStreamingServer[OperationEvent]) error
GetOperations(context.Context, *GetOperationsRequest) (*OperationList, error)
@@ -357,10 +370,13 @@ func (UnimplementedBackrestServer) GetConfig(context.Context, *emptypb.Empty) (*
func (UnimplementedBackrestServer) SetConfig(context.Context, *Config) (*Config, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetConfig not implemented")
}
func (UnimplementedBackrestServer) CheckRepoExists(context.Context, *Repo) (*types.BoolValue, error) {
func (UnimplementedBackrestServer) SetupSftp(context.Context, *SetupSftpRequest) (*SetupSftpResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetupSftp not implemented")
}
func (UnimplementedBackrestServer) CheckRepoExists(context.Context, *CheckRepoExistsRequest) (*CheckRepoExistsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckRepoExists not implemented")
}
func (UnimplementedBackrestServer) AddRepo(context.Context, *Repo) (*Config, error) {
func (UnimplementedBackrestServer) AddRepo(context.Context, *AddRepoRequest) (*Config, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddRepo not implemented")
}
func (UnimplementedBackrestServer) RemoveRepo(context.Context, *types.StringValue) (*Config, error) {
@@ -468,8 +484,26 @@ func _Backrest_SetConfig_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _Backrest_SetupSftp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetupSftpRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackrestServer).SetupSftp(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Backrest_SetupSftp_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackrestServer).SetupSftp(ctx, req.(*SetupSftpRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Backrest_CheckRepoExists_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Repo)
in := new(CheckRepoExistsRequest)
if err := dec(in); err != nil {
return nil, err
}
@@ -481,13 +515,13 @@ func _Backrest_CheckRepoExists_Handler(srv interface{}, ctx context.Context, dec
FullMethod: Backrest_CheckRepoExists_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackrestServer).CheckRepoExists(ctx, req.(*Repo))
return srv.(BackrestServer).CheckRepoExists(ctx, req.(*CheckRepoExistsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Backrest_AddRepo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Repo)
in := new(AddRepoRequest)
if err := dec(in); err != nil {
return nil, err
}
@@ -499,7 +533,7 @@ func _Backrest_AddRepo_Handler(srv interface{}, ctx context.Context, dec func(in
FullMethod: Backrest_AddRepo_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackrestServer).AddRepo(ctx, req.(*Repo))
return srv.(BackrestServer).AddRepo(ctx, req.(*AddRepoRequest))
}
return interceptor(ctx, in, info, handler)
}
@@ -793,6 +827,10 @@ var Backrest_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetConfig",
Handler: _Backrest_SetConfig_Handler,
},
{
MethodName: "SetupSftp",
Handler: _Backrest_SetupSftp_Handler,
},
{
MethodName: "CheckRepoExists",
Handler: _Backrest_CheckRepoExists_Handler,
+40 -12
View File
@@ -39,6 +39,8 @@ const (
BackrestGetConfigProcedure = "/v1.Backrest/GetConfig"
// BackrestSetConfigProcedure is the fully-qualified name of the Backrest's SetConfig RPC.
BackrestSetConfigProcedure = "/v1.Backrest/SetConfig"
// BackrestSetupSftpProcedure is the fully-qualified name of the Backrest's SetupSftp RPC.
BackrestSetupSftpProcedure = "/v1.Backrest/SetupSftp"
// BackrestCheckRepoExistsProcedure is the fully-qualified name of the Backrest's CheckRepoExists
// RPC.
BackrestCheckRepoExistsProcedure = "/v1.Backrest/CheckRepoExists"
@@ -86,8 +88,9 @@ const (
type BackrestClient interface {
GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.Config], error)
SetConfig(context.Context, *connect.Request[v1.Config]) (*connect.Response[v1.Config], error)
CheckRepoExists(context.Context, *connect.Request[v1.Repo]) (*connect.Response[types.BoolValue], error)
AddRepo(context.Context, *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error)
SetupSftp(context.Context, *connect.Request[v1.SetupSftpRequest]) (*connect.Response[v1.SetupSftpResponse], error)
CheckRepoExists(context.Context, *connect.Request[v1.CheckRepoExistsRequest]) (*connect.Response[v1.CheckRepoExistsResponse], error)
AddRepo(context.Context, *connect.Request[v1.AddRepoRequest]) (*connect.Response[v1.Config], error)
RemoveRepo(context.Context, *connect.Request[types.StringValue]) (*connect.Response[v1.Config], error)
GetOperationEvents(context.Context, *connect.Request[emptypb.Empty]) (*connect.ServerStreamForClient[v1.OperationEvent], error)
GetOperations(context.Context, *connect.Request[v1.GetOperationsRequest]) (*connect.Response[v1.OperationList], error)
@@ -140,13 +143,19 @@ func NewBackrestClient(httpClient connect.HTTPClient, baseURL string, opts ...co
connect.WithSchema(backrestMethods.ByName("SetConfig")),
connect.WithClientOptions(opts...),
),
checkRepoExists: connect.NewClient[v1.Repo, types.BoolValue](
setupSftp: connect.NewClient[v1.SetupSftpRequest, v1.SetupSftpResponse](
httpClient,
baseURL+BackrestSetupSftpProcedure,
connect.WithSchema(backrestMethods.ByName("SetupSftp")),
connect.WithClientOptions(opts...),
),
checkRepoExists: connect.NewClient[v1.CheckRepoExistsRequest, v1.CheckRepoExistsResponse](
httpClient,
baseURL+BackrestCheckRepoExistsProcedure,
connect.WithSchema(backrestMethods.ByName("CheckRepoExists")),
connect.WithClientOptions(opts...),
),
addRepo: connect.NewClient[v1.Repo, v1.Config](
addRepo: connect.NewClient[v1.AddRepoRequest, v1.Config](
httpClient,
baseURL+BackrestAddRepoProcedure,
connect.WithSchema(backrestMethods.ByName("AddRepo")),
@@ -255,8 +264,9 @@ func NewBackrestClient(httpClient connect.HTTPClient, baseURL string, opts ...co
type backrestClient struct {
getConfig *connect.Client[emptypb.Empty, v1.Config]
setConfig *connect.Client[v1.Config, v1.Config]
checkRepoExists *connect.Client[v1.Repo, types.BoolValue]
addRepo *connect.Client[v1.Repo, v1.Config]
setupSftp *connect.Client[v1.SetupSftpRequest, v1.SetupSftpResponse]
checkRepoExists *connect.Client[v1.CheckRepoExistsRequest, v1.CheckRepoExistsResponse]
addRepo *connect.Client[v1.AddRepoRequest, v1.Config]
removeRepo *connect.Client[types.StringValue, v1.Config]
getOperationEvents *connect.Client[emptypb.Empty, v1.OperationEvent]
getOperations *connect.Client[v1.GetOperationsRequest, v1.OperationList]
@@ -285,13 +295,18 @@ func (c *backrestClient) SetConfig(ctx context.Context, req *connect.Request[v1.
return c.setConfig.CallUnary(ctx, req)
}
// SetupSftp calls v1.Backrest.SetupSftp.
func (c *backrestClient) SetupSftp(ctx context.Context, req *connect.Request[v1.SetupSftpRequest]) (*connect.Response[v1.SetupSftpResponse], error) {
return c.setupSftp.CallUnary(ctx, req)
}
// CheckRepoExists calls v1.Backrest.CheckRepoExists.
func (c *backrestClient) CheckRepoExists(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[types.BoolValue], error) {
func (c *backrestClient) CheckRepoExists(ctx context.Context, req *connect.Request[v1.CheckRepoExistsRequest]) (*connect.Response[v1.CheckRepoExistsResponse], error) {
return c.checkRepoExists.CallUnary(ctx, req)
}
// AddRepo calls v1.Backrest.AddRepo.
func (c *backrestClient) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error) {
func (c *backrestClient) AddRepo(ctx context.Context, req *connect.Request[v1.AddRepoRequest]) (*connect.Response[v1.Config], error) {
return c.addRepo.CallUnary(ctx, req)
}
@@ -379,8 +394,9 @@ func (c *backrestClient) GetSummaryDashboard(ctx context.Context, req *connect.R
type BackrestHandler interface {
GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.Config], error)
SetConfig(context.Context, *connect.Request[v1.Config]) (*connect.Response[v1.Config], error)
CheckRepoExists(context.Context, *connect.Request[v1.Repo]) (*connect.Response[types.BoolValue], error)
AddRepo(context.Context, *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error)
SetupSftp(context.Context, *connect.Request[v1.SetupSftpRequest]) (*connect.Response[v1.SetupSftpResponse], error)
CheckRepoExists(context.Context, *connect.Request[v1.CheckRepoExistsRequest]) (*connect.Response[v1.CheckRepoExistsResponse], error)
AddRepo(context.Context, *connect.Request[v1.AddRepoRequest]) (*connect.Response[v1.Config], error)
RemoveRepo(context.Context, *connect.Request[types.StringValue]) (*connect.Response[v1.Config], error)
GetOperationEvents(context.Context, *connect.Request[emptypb.Empty], *connect.ServerStream[v1.OperationEvent]) error
GetOperations(context.Context, *connect.Request[v1.GetOperationsRequest]) (*connect.Response[v1.OperationList], error)
@@ -429,6 +445,12 @@ func NewBackrestHandler(svc BackrestHandler, opts ...connect.HandlerOption) (str
connect.WithSchema(backrestMethods.ByName("SetConfig")),
connect.WithHandlerOptions(opts...),
)
backrestSetupSftpHandler := connect.NewUnaryHandler(
BackrestSetupSftpProcedure,
svc.SetupSftp,
connect.WithSchema(backrestMethods.ByName("SetupSftp")),
connect.WithHandlerOptions(opts...),
)
backrestCheckRepoExistsHandler := connect.NewUnaryHandler(
BackrestCheckRepoExistsProcedure,
svc.CheckRepoExists,
@@ -543,6 +565,8 @@ func NewBackrestHandler(svc BackrestHandler, opts ...connect.HandlerOption) (str
backrestGetConfigHandler.ServeHTTP(w, r)
case BackrestSetConfigProcedure:
backrestSetConfigHandler.ServeHTTP(w, r)
case BackrestSetupSftpProcedure:
backrestSetupSftpHandler.ServeHTTP(w, r)
case BackrestCheckRepoExistsProcedure:
backrestCheckRepoExistsHandler.ServeHTTP(w, r)
case BackrestAddRepoProcedure:
@@ -596,11 +620,15 @@ func (UnimplementedBackrestHandler) SetConfig(context.Context, *connect.Request[
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.SetConfig is not implemented"))
}
func (UnimplementedBackrestHandler) CheckRepoExists(context.Context, *connect.Request[v1.Repo]) (*connect.Response[types.BoolValue], error) {
func (UnimplementedBackrestHandler) SetupSftp(context.Context, *connect.Request[v1.SetupSftpRequest]) (*connect.Response[v1.SetupSftpResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.SetupSftp is not implemented"))
}
func (UnimplementedBackrestHandler) CheckRepoExists(context.Context, *connect.Request[v1.CheckRepoExistsRequest]) (*connect.Response[v1.CheckRepoExistsResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.CheckRepoExists is not implemented"))
}
func (UnimplementedBackrestHandler) AddRepo(context.Context, *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error) {
func (UnimplementedBackrestHandler) AddRepo(context.Context, *connect.Request[v1.AddRepoRequest]) (*connect.Response[v1.Config], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.AddRepo is not implemented"))
}
+2
View File
@@ -54,10 +54,12 @@ require (
github.com/go-stack/stack v1.8.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/josephspurrier/goversioninfo v1.5.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pkg/sftp v1.13.10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.2 // indirect
+4
View File
@@ -90,6 +90,8 @@ github.com/josephspurrier/goversioninfo v1.5.0 h1:9TJtORoyf4YMoWSOo/cXFN9A/lB3Pn
github.com/josephspurrier/goversioninfo v1.5.0/go.mod h1:6MoTvFZ6GKJkzcdLnU5T/RGYUbHQbKpYeNP0AgQLd2o=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -124,6 +126,8 @@ github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzL
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+373 -13
View File
@@ -3,11 +3,19 @@ package api
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"slices"
"strings"
"sync"
@@ -17,6 +25,7 @@ import (
"github.com/garethgeorge/backrest/gen/go/types"
v1 "github.com/garethgeorge/backrest/gen/go/v1"
"github.com/garethgeorge/backrest/gen/go/v1/v1connect"
"github.com/garethgeorge/backrest/internal/api/sftputil"
syncapi "github.com/garethgeorge/backrest/internal/api/syncapi"
"github.com/garethgeorge/backrest/internal/config"
"github.com/garethgeorge/backrest/internal/cryptoutil"
@@ -29,7 +38,9 @@ import (
"github.com/garethgeorge/backrest/internal/protoutil"
"github.com/garethgeorge/backrest/internal/resticinstaller"
"github.com/garethgeorge/backrest/pkg/restic"
"github.com/pkg/sftp"
"go.uber.org/zap"
"golang.org/x/crypto/ssh"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -98,21 +109,33 @@ func (s *BackrestHandler) SetConfig(ctx context.Context, req *connect.Request[v1
return connect.NewResponse(newConfig), nil
}
func (s *BackrestHandler) CheckRepoExists(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[types.BoolValue], error) {
func (s *BackrestHandler) CheckRepoExists(ctx context.Context, req *connect.Request[v1.CheckRepoExistsRequest]) (*connect.Response[v1.CheckRepoExistsResponse], error) {
c, err := s.config.Get()
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}
sanitizeRepoFlags(req.Msg.Repo)
c = proto.Clone(c).(*v1.Config)
if idx := slices.IndexFunc(c.Repos, func(r *v1.Repo) bool { return r.Id == req.Msg.Id }); idx != -1 {
c.Repos[idx] = req.Msg
if idx := slices.IndexFunc(c.Repos, func(r *v1.Repo) bool { return r.Id == req.Msg.Repo.Id }); idx != -1 {
c.Repos[idx] = req.Msg.Repo
} else {
c.Repos = append(c.Repos, req.Msg)
c.Repos = append(c.Repos, req.Msg.Repo)
}
if req.Msg.Guid == "" {
req.Msg.Guid = cryptoutil.MustRandomID(cryptoutil.DefaultIDBits)
if req.Msg.Repo.Guid == "" {
req.Msg.Repo.Guid = cryptoutil.MustRandomID(cryptoutil.DefaultIDBits)
}
if err := s.addSftpHostKey(req.Msg.Repo, req.Msg.GetTrustSftpHostKey()); err != nil {
if strings.Contains(err.Error(), "host key verification failed") {
return connect.NewResponse(&v1.CheckRepoExistsResponse{
HostKeyUntrusted: true,
Error: err.Error(),
}), nil
}
return nil, fmt.Errorf("failed to add sftp host key: %w", err)
}
if err := config.ValidateConfig(c); err != nil {
@@ -124,7 +147,7 @@ func (s *BackrestHandler) CheckRepoExists(ctx context.Context, req *connect.Requ
return nil, fmt.Errorf("failed to find or install restic binary: %w", err)
}
r, err := repo.NewRepoOrchestrator(c, req.Msg, bin)
r, err := repo.NewRepoOrchestrator(c, req.Msg.Repo, bin)
if err != nil {
return nil, fmt.Errorf("failed to configure repo: %w", err)
}
@@ -133,24 +156,25 @@ func (s *BackrestHandler) CheckRepoExists(ctx context.Context, req *connect.Requ
defer cancel()
if err := r.Exists(ctx); err != nil {
zap.S().Debugf("repo %q exists or not: %v", req.Msg.Id, err)
zap.S().Debugf("repo %q exists or not: %v", req.Msg.Repo.Id, err)
if errors.Is(err, restic.ErrRepoNotFound) {
zap.S().Debugf("repo %q does not exist", req.Msg.Id)
return connect.NewResponse(&types.BoolValue{Value: false}), nil
zap.S().Debugf("repo %q does not exist", req.Msg.Repo.Id)
return connect.NewResponse(&v1.CheckRepoExistsResponse{Exists: false}), nil
}
return nil, err
}
return connect.NewResponse(&types.BoolValue{Value: true}), nil
return connect.NewResponse(&v1.CheckRepoExistsResponse{Exists: true}), nil
}
// AddRepo implements POST /v1/config/repo, it includes validation that the repo can be initialized.
func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error) {
func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.AddRepoRequest]) (*connect.Response[v1.Config], error) {
c, err := s.config.Get()
if err != nil {
return nil, fmt.Errorf("failed to get config: %w", err)
}
newRepo := req.Msg
newRepo := req.Msg.Repo
sanitizeRepoFlags(newRepo)
// Deep copy the configuration
c = proto.Clone(c).(*v1.Config)
@@ -189,6 +213,10 @@ func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.R
newRepo.Guid = guid
if err := s.addSftpHostKey(newRepo, req.Msg.GetTrustSftpHostKey()); err != nil {
return nil, fmt.Errorf("failed to add sftp host key: %w", err)
}
if err := config.ValidateConfig(c); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
@@ -267,6 +295,158 @@ func (s *BackrestHandler) RemoveRepo(ctx context.Context, req *connect.Request[t
return connect.NewResponse(cfg), nil
}
// SetupSftp implements SetupSftp RPC
func (s *BackrestHandler) SetupSftp(ctx context.Context, req *connect.Request[v1.SetupSftpRequest]) (*connect.Response[v1.SetupSftpResponse], error) {
host := req.Msg.Host
port := req.Msg.Port
if port == "" {
port = "22"
}
user := req.Msg.Username
password := req.Msg.Password // Optional
if runtime.GOOS == "windows" {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("automated SFTP setup is not supported on Windows"))
}
// 1. Host Key Verification/Addition
if err := sftputil.AddHostKey(host, port, env.SSHDir()); err != nil {
return connect.NewResponse(&v1.SetupSftpResponse{
Error: fmt.Sprintf("Failed to add host key: %v", err),
}), nil
}
// 2. Generate Key
_, pubBytes, keyPath, err := sftputil.GenerateKey(host, env.SSHDir())
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
pubKeyStr := string(pubBytes)
// 3. Install if password provided
if password != nil {
if err := sftputil.InstallKey(host, port, user, *password, pubBytes); err != nil {
return connect.NewResponse(&v1.SetupSftpResponse{
Error: fmt.Sprintf("Failed to install key: %v", err),
}), nil
}
// Verify
privPEM, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read generated private key for verification: %w", err)
}
if err := sftputil.VerifyConnection(host, port, user, privPEM); err != nil {
return connect.NewResponse(&v1.SetupSftpResponse{
Error: fmt.Sprintf("Key installed but verification failed: %v", err),
}), nil
}
}
return connect.NewResponse(&v1.SetupSftpResponse{
PublicKey: pubKeyStr,
KeyPath: keyPath,
KnownHostsPath: filepath.Join(env.SSHDir(), "known_hosts"),
}), nil
}
// This is equivalent to what `ssh-keyscan` does.
func (s *BackrestHandler) addSftpHostKey(repo *v1.Repo, trust bool) error {
uri := repo.GetUri()
if !strings.HasPrefix(uri, "sftp:") {
return nil
}
uri = strings.TrimPrefix(uri, "sftp:")
slashIdx := strings.Index(uri, "/")
if slashIdx == -1 {
slashIdx = len(uri)
}
authority := uri[:slashIdx]
hostPart := authority
if atIdx := strings.LastIndex(authority, "@"); atIdx != -1 {
hostPart = authority[atIdx+1:]
}
host, port, err := net.SplitHostPort(hostPart)
if err != nil {
host = hostPart
}
if host == "" {
return errors.New("could not parse host from sftp uri")
}
// Extract port from flags if possible
// Check for port in sftp.args
// e.g. --option=sftp.args='-oBatchMode=yes -p 23'
re := regexp.MustCompile(`(?:^|\s)-[pP]\s*(\d+)`)
for _, flag := range repo.Flags {
if strings.HasPrefix(flag, "--option=sftp.args=") {
matches := re.FindStringSubmatch(flag)
if len(matches) > 1 {
port = matches[1]
break
}
}
}
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("could not get home directory: %w", err)
}
knownHostsPath := path.Join(home, ".ssh", "known_hosts")
if err := os.MkdirAll(path.Dir(knownHostsPath), 0700); err != nil {
return err
}
// Construct host spec for ssh-keygen and ssh-keyscan
// If port is non-standard, host spec is usually [host]:port
hostSpec := host
if port != "" && port != "22" {
hostSpec = fmt.Sprintf("[%s]:%s", host, port)
}
checkCmd := exec.Command("ssh-keygen", "-F", hostSpec)
if err := checkCmd.Run(); err == nil {
zap.S().Debugf("SFTP host %s already in known_hosts", hostSpec)
return nil
}
if !trust {
return fmt.Errorf("SFTP host key verification failed: key for host %s is unknown", hostSpec)
}
keyscanArgs := []string{"-H"}
if port != "" {
keyscanArgs = append(keyscanArgs, "-p", port)
}
keyscanArgs = append(keyscanArgs, host)
keyscanCmd := exec.Command("ssh-keyscan", keyscanArgs...)
keyOutput, err := keyscanCmd.Output()
if err != nil {
return fmt.Errorf("ssh-keyscan for host %s failed: %w", host, err)
}
f, err := os.OpenFile(knownHostsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("failed to open known_hosts file: %w", err)
}
defer f.Close()
if _, err := f.Write(keyOutput); err != nil {
return fmt.Errorf("failed to write to known_hosts file: %w", err)
}
zap.S().Infof("Added SFTP host %s to known_hosts file at %s", hostSpec, knownHostsPath)
return nil
}
// ListSnapshots implements POST /v1/snapshots
func (s *BackrestHandler) ListSnapshots(ctx context.Context, req *connect.Request[v1.ListSnapshotsRequest]) (*connect.Response[v1.ResticSnapshotList], error) {
query := req.Msg
@@ -904,3 +1084,183 @@ func (s *BackrestHandler) GetSummaryDashboard(ctx context.Context, req *connect.
return connect.NewResponse(response), nil
}
func parseSftpConfig(repo *v1.Repo) (user, host, port string, err error) {
uri := repo.GetUri()
if !strings.HasPrefix(uri, "sftp:") {
return "", "", "", errors.New("not an sftp repo")
}
uri = strings.TrimPrefix(uri, "sftp:")
slashIdx := strings.Index(uri, "/")
if slashIdx == -1 {
slashIdx = len(uri)
}
authority := uri[:slashIdx]
hostPart := authority
if atIdx := strings.LastIndex(authority, "@"); atIdx != -1 {
user = authority[:atIdx]
hostPart = authority[atIdx+1:]
}
host, port, err = net.SplitHostPort(hostPart)
if err != nil {
host = hostPart
}
// Parse port from flags
re := regexp.MustCompile(`(?:^|\s)-[pP]\s*(\d+)`)
for _, flag := range repo.Flags {
if strings.HasPrefix(flag, "--option=sftp.args=") {
matches := re.FindStringSubmatch(flag)
if len(matches) > 1 {
port = matches[1]
break
}
}
}
return user, host, port, nil
}
func sanitizeRepoFlags(repo *v1.Repo) {
for i, flag := range repo.Flags {
if strings.HasPrefix(flag, "--option=sftp.args=") {
repo.Flags[i] = strings.ReplaceAll(flag, "-i @", "-i ")
}
}
}
func (s *BackrestHandler) generateAndInstallSSHKey(host, port, user, password string) (string, error) {
zap.S().Debugf("Generating ED25519 key for %s@%s:%s", user, host, port)
// Generate Key
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return "", err
}
privBlock, err := ssh.MarshalPrivateKey(priv, "")
if err != nil {
return "", fmt.Errorf("failed to marshal private key: %w", err)
}
privPEM := pem.EncodeToMemory(privBlock)
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return "", err
}
pubBytes := ssh.MarshalAuthorizedKey(sshPub)
// Save to file
keyDir := env.SSHDir()
if err := os.MkdirAll(keyDir, 0700); err != nil {
return "", err
}
keyPath := path.Join(keyDir, fmt.Sprintf("id_ed25519_%s_%d", host, time.Now().Unix()))
if err := os.WriteFile(keyPath, privPEM, 0600); err != nil {
return "", err
}
if err := os.WriteFile(keyPath+".pub", pubBytes, 0644); err != nil {
zap.S().Warnf("failed to write public key: %v", err)
}
zap.S().Debugf("Saved private key to %s", keyPath)
// Verify key file is readable and valid
if diskBytes, err := os.ReadFile(keyPath); err != nil {
return "", fmt.Errorf("failed to read back key file: %w", err)
} else if _, err := ssh.ParsePrivateKey(diskBytes); err != nil {
return "", fmt.Errorf("generated key file is invalid or unparseable: %w", err)
}
// Install on server
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
zap.S().Debugf("Dialing SSH to %s:%s to install key", host, port)
conn, err := ssh.Dial("tcp", net.JoinHostPort(host, port), sshConfig)
if err != nil {
return "", fmt.Errorf("failed to dial ssh: %w", err)
}
defer conn.Close()
// Use SFTP to install key (handles restricted shells better)
sftpClient, err := sftp.NewClient(conn)
if err != nil {
return "", fmt.Errorf("failed to create sftp client: %w", err)
}
defer sftpClient.Close()
// Ensure .ssh directory exists
if _, err := sftpClient.Stat(".ssh"); errors.Is(err, os.ErrNotExist) {
if err := sftpClient.Mkdir(".ssh"); err != nil {
return "", fmt.Errorf("failed to create .ssh directory: %w", err)
}
if err := sftpClient.Chmod(".ssh", 0700); err != nil {
zap.S().Warnf("failed to chmod .ssh: %v", err)
}
}
// Open authorized_keys for appending
f, err := sftpClient.OpenFile(".ssh/authorized_keys", os.O_APPEND|os.O_CREATE|os.O_WRONLY)
if err != nil {
return "", fmt.Errorf("failed to open .ssh/authorized_keys: %w", err)
}
defer f.Close()
if err := f.Chmod(0600); err != nil {
zap.S().Warnf("failed to chmod authorized_keys: %v", err)
}
// Append key with leading newline to be safe
if _, err := f.Write([]byte("\n")); err != nil {
return "", fmt.Errorf("failed to write newline to authorized_keys: %w", err)
}
if _, err := f.Write(pubBytes); err != nil {
return "", fmt.Errorf("failed to write key to authorized_keys: %w", err)
}
if _, err := f.Write([]byte("\n")); err != nil {
return "", fmt.Errorf("failed to write newline to authorized_keys: %w", err)
}
zap.S().Debug("Key installed successfully via SFTP")
// Verify key works
keySigner, err := ssh.NewSignerFromKey(priv)
if err != nil {
return "", fmt.Errorf("failed to create signer: %w", err)
}
testConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(keySigner),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
zap.S().Debugf("Verifying connection with new key...")
testConn, err := ssh.Dial("tcp", net.JoinHostPort(host, port), testConfig)
if err != nil {
zap.S().Warnf("Verification failed: %v", err)
return "", fmt.Errorf("generated key was installed but failed to authenticate: %w", err)
}
defer testConn.Close()
// Verify SFTP subsystem
verifySftpClient, err := sftp.NewClient(testConn)
if err != nil {
zap.S().Warnf("SFTP verification failed: %v", err)
return "", fmt.Errorf("generated key authenticated but SFTP subsystem failed: %w", err)
}
verifySftpClient.Close()
zap.S().Debug("Verification successful")
return keyPath, nil
}
+210
View File
@@ -0,0 +1,210 @@
package sftputil
import (
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"errors"
"fmt"
"net"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
"github.com/pkg/sftp"
"go.uber.org/zap"
"golang.org/x/crypto/ssh"
)
// AddHostKey adds the SFTP host key to the known_hosts file.
// It uses ssh-keyscan to fetch the key.
func AddHostKey(host, port string, sshDir string) error {
hostSpec := host
if port != "" && port != "22" {
hostSpec = fmt.Sprintf("[%s]:%s", host, port)
}
knownHostsPath := filepath.Join(sshDir, "known_hosts")
if err := os.MkdirAll(path.Dir(knownHostsPath), 0700); err != nil {
return fmt.Errorf("failed to create ssh dir: %w", err)
}
// Check if already known
checkCmd := exec.Command("ssh-keygen", "-F", hostSpec, "-f", knownHostsPath)
if checkCmd.Run() == nil {
zap.S().Debugf("SFTP host %s already in known_hosts", hostSpec)
return nil
}
if err := os.MkdirAll(path.Dir(knownHostsPath), 0700); err != nil {
return fmt.Errorf("failed to create ssh dir: %w", err)
}
keyscanArgs := []string{"-H"}
if port != "" {
keyscanArgs = append(keyscanArgs, "-p", port)
}
keyscanArgs = append(keyscanArgs, host)
keyscanCmd := exec.Command("ssh-keyscan", keyscanArgs...)
keyOutput, err := keyscanCmd.Output()
if err != nil {
return fmt.Errorf("ssh-keyscan for host %s failed: %w", host, err)
}
f, err := os.OpenFile(knownHostsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("failed to open known_hosts file: %w", err)
}
defer f.Close()
if _, err := f.Write(keyOutput); err != nil {
return fmt.Errorf("failed to write to known_hosts file: %w", err)
}
zap.S().Infof("Added SFTP host %s to known_hosts file at %s", hostSpec, knownHostsPath)
return nil
}
// GenerateKey generates an Ed25519 key pair and saves it to the specified directory.
// Returns the private key in OpenSSH PEM format, public key in SSH format, and the full path to the private key file.
func GenerateKey(host string, sshDir string) ([]byte, []byte, string, error) {
zap.S().Debugf("Generating ED25519 key for host %s", host)
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, "", fmt.Errorf("failed to generate key: %w", err)
}
// Marshal private key to OpenSSH PEM format (requires "golang.org/x/crypto/ssh")
// Note: ssh.MarshalPrivateKey returns a PEM block since Go 1.16+ for Ed25519?
// Actually ssh.MarshalPrivateKey returns an *pem.Block.
privBlock, err := ssh.MarshalPrivateKey(priv, "")
if err != nil {
return nil, nil, "", fmt.Errorf("failed to marshal private key: %w", err)
}
privPEM := pem.EncodeToMemory(privBlock)
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return nil, nil, "", fmt.Errorf("failed to create public key: %w", err)
}
pubBytes := ssh.MarshalAuthorizedKey(sshPub)
// Save to file
if err := os.MkdirAll(sshDir, 0700); err != nil {
return nil, nil, "", fmt.Errorf("failed to create ssh dir: %w", err)
}
sanitizedHost := strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '.' || r == '-' {
return r
}
return '_'
}, host)
keyPath := filepath.Join(sshDir, "id_ed25519_"+string(sanitizedHost))
// check if file exists
if _, err := os.Stat(keyPath); err == nil {
// read the key from disk instead
privPEM, err = os.ReadFile(keyPath)
if err != nil {
return nil, nil, "", fmt.Errorf("failed to read private key: %w", err)
}
pubBytes, err = os.ReadFile(keyPath + ".pub")
if err != nil {
return nil, nil, "", fmt.Errorf("failed to read public key: %w", err)
}
return privPEM, pubBytes, keyPath, nil
}
if err := os.WriteFile(keyPath, privPEM, 0600); err != nil {
return nil, nil, "", fmt.Errorf("failed to write private key: %w", err)
}
if err := os.WriteFile(keyPath+".pub", pubBytes, 0644); err != nil {
zap.S().Warnf("failed to write public key: %v", err)
}
return privPEM, pubBytes, keyPath, nil
}
// InstallKey connects to the SFTP server using a password and appends the public key to authorized_keys.
func InstallKey(host, port, user, password string, pubBytes []byte) error {
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Verification assumed done via AddHostKey
Timeout: 10 * time.Second,
}
conn, err := ssh.Dial("tcp", net.JoinHostPort(host, port), sshConfig)
if err != nil {
return fmt.Errorf("failed to connect with password: %w", err)
}
defer conn.Close()
sftpClient, err := sftp.NewClient(conn)
if err != nil {
return fmt.Errorf("failed to create sftp client: %w", err)
}
defer sftpClient.Close()
// Ensure .ssh directory exists
if _, err := sftpClient.Stat(".ssh"); errors.Is(err, os.ErrNotExist) {
if err := sftpClient.Mkdir(".ssh"); err != nil {
return fmt.Errorf("failed to create .ssh directory: %w", err)
}
if err := sftpClient.Chmod(".ssh", 0700); err != nil {
zap.S().Warnf("failed to chmod .ssh: %v", err)
}
}
f, err := sftpClient.OpenFile(".ssh/authorized_keys", os.O_APPEND|os.O_CREATE|os.O_WRONLY)
if err != nil {
return fmt.Errorf("failed to open authorized_keys: %w", err)
}
defer f.Close()
if err := f.Chmod(0600); err != nil {
zap.S().Warnf("failed to chmod authorized_keys: %v", err)
}
if _, err := f.Write([]byte("\n")); err != nil {
return fmt.Errorf("write error: %w", err)
}
if _, err := f.Write(pubBytes); err != nil {
return fmt.Errorf("write error: %w", err)
}
if _, err := f.Write([]byte("\n")); err != nil {
return fmt.Errorf("write error: %w", err)
}
return nil
}
// VerifyConnection attempts to connect using the provided private key.
func VerifyConnection(host, port, user string, privPEM []byte) error {
signer, err := ssh.ParsePrivateKey(privPEM)
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
clientConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
conn, err := ssh.Dial("tcp", net.JoinHostPort(host, port), clientConfig)
if err != nil {
return fmt.Errorf("verification connection failed: %w", err)
}
defer conn.Close()
return nil
}
+84
View File
@@ -0,0 +1,84 @@
package sftputil
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestGenerateKey_ReuseAndSanitization(t *testing.T) {
// Create a temporary directory for SSH keys
tempDir, err := os.MkdirTemp("", "sftp_test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
host := "example.com"
// First call: should generate new keys
priv1, pub1, path1, err := GenerateKey(host, tempDir)
if err != nil {
t.Fatalf("GenerateKey failed: %v", err)
}
if priv1 == nil || pub1 == nil {
t.Fatal("GenerateKey returned nil keys")
}
// Verify file existence
expectedFilename := "id_ed25519_example.com"
if filepath.Base(path1) != expectedFilename {
t.Errorf("expected filename %s, got %s", expectedFilename, filepath.Base(path1))
}
if _, err := os.Stat(path1); os.IsNotExist(err) {
t.Errorf("private key file does not exist at %s", path1)
}
if _, err := os.Stat(path1 + ".pub"); os.IsNotExist(err) {
t.Errorf("public key file does not exist at %s.pub", path1)
}
// Second call: should reuse existing keys
priv2, pub2, path2, err := GenerateKey(host, tempDir)
if err != nil {
t.Fatalf("GenerateKey (2nd call) failed: %v", err)
}
// Verify keys are identical
if !bytes.Equal(priv1, priv2) {
t.Error("GenerateKey did not reuse the private key")
}
if !bytes.Equal(pub1, pub2) {
t.Error("GenerateKey did not reuse the public key")
}
if path1 != path2 {
t.Errorf("GenerateKey returned different paths: %s vs %s", path1, path2)
}
}
func TestGenerateKey_Sanitization(t *testing.T) {
tempDir, err := os.MkdirTemp("", "sftp_retry_test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
// Host with special characters
unsafeHost := "bad/host:name!@#"
_, _, keyPath, err := GenerateKey(unsafeHost, tempDir)
if err != nil {
t.Fatalf("GenerateKey failed for unsafe host: %v", err)
}
// Expected sanitization: bad_host_name___
// characters allowed: a-z, A-Z, 0-9, ., -
// '/' -> '_'
// ':' -> '_'
// '!' -> '_'
// '@' -> '_'
// '#' -> '_'
expectedFilename := "id_ed25519_bad_host_name___"
if filepath.Base(keyPath) != expectedFilename {
t.Errorf("Sanitization failed. Expected %s, got %s", expectedFilename, filepath.Base(keyPath))
}
}
+5
View File
@@ -99,6 +99,11 @@ func LogsPath() string {
return filepath.Join(dataDir, "processlogs")
}
func SSHDir() string {
// This is awkward, we don't have a flag that specifies a "config" directory persay, so we default to the directory containing the config file for our SSH keys/known_hosts file.
return filepath.Join(filepath.Dir(ConfigFilePath()), ".backrest-ssh")
}
func getHomeDir() string {
home, err := os.UserHomeDir()
if err != nil {
+34 -2
View File
@@ -16,9 +16,11 @@ service Backrest {
rpc SetConfig (Config) returns (Config) {}
rpc CheckRepoExists (Repo) returns (types.BoolValue) {} // returns an error if the repo does not exist
rpc SetupSftp (SetupSftpRequest) returns (SetupSftpResponse) {}
rpc AddRepo (Repo) returns (Config) {}
rpc CheckRepoExists (CheckRepoExistsRequest) returns (CheckRepoExistsResponse) {} // returns an error if the repo does not exist
rpc AddRepo (AddRepoRequest) returns (Config) {}
rpc RemoveRepo (types.StringValue) returns (Config) {} // remvoes a repo from the config and deletes its history, does not delete the repo on disk
@@ -76,6 +78,36 @@ message OpSelector {
optional int64 modno_gte = 9;
}
message SetupSftpRequest {
string host = 1;
string port = 2;
string username = 3;
optional string password = 4; // If not provided, we only generate the key and add host to known_hosts
}
message SetupSftpResponse {
string public_key = 1;
string key_path = 2;
string known_hosts_path = 3;
string error = 4;
}
message CheckRepoExistsRequest {
Repo repo = 1;
bool trust_sftp_host_key = 2;
}
message CheckRepoExistsResponse {
bool exists = 1;
string error = 2;
bool host_key_untrusted = 5;
}
message AddRepoRequest {
Repo repo = 1;
bool trust_sftp_host_key = 2;
}
message DoRepoTaskRequest {
string repo_id = 1;
enum Task {
+6 -4
View File
@@ -115,10 +115,12 @@ func TestFirstRun(t *testing.T) {
fmt.Sprintf("http://%s", addr),
)
req := connect.NewRequest(&v1.Repo{
Id: "test-repo",
Uri: filepath.Join(tmpDir, "test-repo"),
Password: "1234",
req := connect.NewRequest(&v1.AddRepoRequest{
Repo: &v1.Repo{
Id: "test-repo",
Uri: filepath.Join(tmpDir, "test-repo"),
Password: "1234",
},
})
_, err := client.AddRepo(context.Background(), req)
if err != nil {
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff