From 09c2cc5395745c50865d020b78b854cac7cf8150 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:19:20 +0300 Subject: [PATCH 01/88] fix: delete extra var err --- pkg/kernel/create.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/kernel/create.go b/pkg/kernel/create.go index 91b6dbd..3f5cd81 100644 --- a/pkg/kernel/create.go +++ b/pkg/kernel/create.go @@ -12,7 +12,6 @@ import ( var ( ErrConnectionByAliasExistsAtCreate = errors.New("connection by alias exists") ErrDeletePrivateKeyAtCreate = errors.New("err delete private key") - ErrSavePrivateKeyAtCreate = errors.New("err save private key") ErrGetConnectionAtCreate = errors.New("err get connection") ErrSetConnectionAtCreate = errors.New("err set connection") ) From 3c00e3bdf3ed5e5788abee2a37183dc1beeebc7c Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:45:35 +0300 Subject: [PATCH 02/88] feat: add todo --- internal/store/connection.go | 31 +++++++++++++++++++++++-------- pkg/connect/connect.go | 13 +++++++++++-- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/internal/store/connection.go b/internal/store/connection.go index 6a5d2d8..7f07f6e 100644 --- a/internal/store/connection.go +++ b/internal/store/connection.go @@ -24,29 +24,44 @@ var ( ErrDecryptData = errors.New("failed to decrypt data") ) -// GetConnections get connection from file -func GetConnections() (*connect.Connections, error) { - var connections connect.Connections - +func getFromLocalStorage() (string, error) { cryptKey, err := GetCryptKey() if err != nil { logger.Error(err.Error()) - return nil, err + return "", err } encryptedConnections, err := storage.Get(DirectionApp, FileConnections) if err != nil { logger.Error(err.Error()) - return nil, ErrGetConnection + return "", ErrGetConnection } decryptedConnections, err := crypto.Decrypt(encryptedConnections, cryptKey) if err != nil { logger.Error(err.Error()) - return nil, ErrDecryptData + return "", ErrDecryptData + } + + return decryptedConnections, nil +} + +// todo add logic +func getFromSSHConfig() (string, error) { + return "", nil +} + +// GetConnections get connection from file +func GetConnections() (*connect.Connections, error) { + var connections connect.Connections + + cls, err := getFromLocalStorage() + if err != nil { + logger.Error(err.Error()) + return nil, ErrGetConnection } - err = json.Unmarshal([]byte(decryptedConnections), &connections) + err = json.Unmarshal([]byte(cls), &connections) if err != nil { logger.Error(err.Error()) return nil, ErrUnmarshalJson diff --git a/pkg/connect/connect.go b/pkg/connect/connect.go index e3f465f..0acb87c 100644 --- a/pkg/connect/connect.go +++ b/pkg/connect/connect.go @@ -1,9 +1,15 @@ package connect type ConnectionType string +type FromType string -// TypeSSH type for ssh connection -const TypeSSH ConnectionType = "ssh" +const ( + // TypeSSH type for ssh connection + TypeSSH ConnectionType = "ssh" + + FromLocal FromType = "local" + FromSSHConfig FromType = "ssh-config" +) type Connections struct { Connects []Connect `json:"connects"` @@ -19,6 +25,9 @@ type Connect struct { CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` + //todo it is saved when updating and creating + From FromType `json:"from"` + // Type specifies the connection protocol (e.g., "ssh") Type ConnectionType `json:"type"` From 85727f1a38fc0c5ade2194608104e706a9792dda Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:54:17 +0300 Subject: [PATCH 03/88] fix: rename variable --- pkg/connect/connect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connect/connect.go b/pkg/connect/connect.go index 0acb87c..5be7a0d 100644 --- a/pkg/connect/connect.go +++ b/pkg/connect/connect.go @@ -8,7 +8,7 @@ const ( TypeSSH ConnectionType = "ssh" FromLocal FromType = "local" - FromSSHConfig FromType = "ssh-config" + FromConfigSSH FromType = "config-ssh" ) type Connections struct { From 9febce40af1fb906bfece237019d0a8d1d46bb7c Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:54:30 +0300 Subject: [PATCH 04/88] feat: add logic for get file data from ssh config --- internal/store/connection.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/store/connection.go b/internal/store/connection.go index 7f07f6e..9c313d4 100644 --- a/internal/store/connection.go +++ b/internal/store/connection.go @@ -12,9 +12,11 @@ import ( ) const FileConnections = envconst.FilenameConnections +const FileConfigSSH = envconst.FilenameConfigSSH var ( DirectionApp = storage.GetAppDir() + DirectionSSH = storage.GetDirSSH() ErrEncryptData = errors.New("err encrypt data") ErrMarshalJson = errors.New("failed to marshal json") @@ -46,9 +48,15 @@ func getFromLocalStorage() (string, error) { return decryptedConnections, nil } -// todo add logic -func getFromSSHConfig() (string, error) { - return "", nil +// todo add logic get connection from ssh config +func getFromConfigSSH() (string, error) { + sshConfig, err := storage.Get(DirectionSSH, FileConfigSSH) + if err != nil { + logger.Error(err.Error()) + return "", err + } + + return sshConfig, nil } // GetConnections get connection from file From 8cfb812ababe2e5473e8354778f62ff27dd74524 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:54:38 +0300 Subject: [PATCH 05/88] feat: add new const for ssh config --- configs/envconst/file.go | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/envconst/file.go b/configs/envconst/file.go index 4e1375d..09ca248 100644 --- a/configs/envconst/file.go +++ b/configs/envconst/file.go @@ -2,6 +2,7 @@ package envconst const ( FilenameConnections = "connection.json" + FilenameConfigSSH = "config" FilenameConfig = "configs" FilenameLogger = "log.log" ) From 815d6513aaa866c69b4ff93cddf97f41e47b331a Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:55:02 +0300 Subject: [PATCH 06/88] feat: delete extra method and add logic on get direction ssh --- internal/storage/helper.go | 53 ++++---------------------------------- 1 file changed, 5 insertions(+), 48 deletions(-) diff --git a/internal/storage/helper.go b/internal/storage/helper.go index 60d4724..525afc9 100644 --- a/internal/storage/helper.go +++ b/internal/storage/helper.go @@ -1,14 +1,11 @@ package storage import ( - "errors" + "github.com/misha-ssh/kernel/configs/envconst" + "github.com/misha-ssh/kernel/configs/envname" "os" "os/user" "path/filepath" - "strings" - - "github.com/misha-ssh/kernel/configs/envconst" - "github.com/misha-ssh/kernel/configs/envname" ) const CharHidden = "." @@ -29,54 +26,14 @@ func GetAppDir() string { return filepath.Join(usr.HomeDir, hiddenDir) } -// GetUserPrivateKey get file with ssh keys -func GetUserPrivateKey() ([]string, error) { - var privateKeys []string - +// GetDirSSH get dir ssh +func GetDirSSH() string { homeDir, err := os.UserHomeDir() if err != nil { panic(err) } - listDeniedPatternKeys := []string{ - ".pub", - "known_hosts", - "config", - "authorized_keys", - } - - keysDir := filepath.Join(homeDir, envconst.DirectionsUserPrivateKey) - - keys, err := os.ReadDir(keysDir) - if err != nil || len(keys) == 0 { - return []string{}, errors.New("cannot find user private keys") - } - - for _, key := range keys { - if key.IsDir() { - continue - } - - keyName := key.Name() - - containsPattern := false - for _, pattern := range listDeniedPatternKeys { - if strings.Contains(keyName, pattern) { - containsPattern = true - break - } - } - - if !containsPattern { - privateKeys = append(privateKeys, filepath.Join(keysDir, keyName)) - } - } - - if len(privateKeys) == 0 { - return []string{}, errors.New("cannot find user private keys") - } - - return privateKeys, nil + return filepath.Join(homeDir, envconst.DirectionsUserPrivateKey) } // GetPrivateKeysDir get dir where save private keys From 4520c3037a9fb4d303fa3d3888bbada4dc0b83cd Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 21:55:25 +0300 Subject: [PATCH 07/88] fix: delete extra method --- internal/storage/helper_test.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/internal/storage/helper_test.go b/internal/storage/helper_test.go index 09c3cf3..002f9a8 100644 --- a/internal/storage/helper_test.go +++ b/internal/storage/helper_test.go @@ -6,7 +6,6 @@ import ( "os" "os/user" "path/filepath" - "reflect" "testing" "github.com/misha-ssh/kernel/configs/envconst" @@ -156,25 +155,3 @@ func TestGetPrivateKeysDir(t *testing.T) { }) } } - -func TestGetUserPrivateKey(t *testing.T) { - tests := []struct { - name string - want []string - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetUserPrivateKey() - if (err != nil) != tt.wantErr { - t.Errorf("GetUserPrivateKey() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetUserPrivateKey() got = %v, want %v", got, tt.want) - } - }) - } -} From db095dd0a3d89ab5353d115511b7652a6d119f0a Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 22:00:10 +0300 Subject: [PATCH 08/88] feat: add example func for get connection from config --- pkg/connect/ssh_config.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 pkg/connect/ssh_config.go diff --git a/pkg/connect/ssh_config.go b/pkg/connect/ssh_config.go new file mode 100644 index 0000000..75ef1a4 --- /dev/null +++ b/pkg/connect/ssh_config.go @@ -0,0 +1,6 @@ +package connect + +// FromConfig todo add logic +func FromConfig(config string) (*Connections, error) { + return new(Connections), nil +} From f883bbf9833a0e07ad6175c21693d56cb5a5c699 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 10 Nov 2025 22:00:15 +0300 Subject: [PATCH 09/88] feat: add todo --- internal/store/connection.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/store/connection.go b/internal/store/connection.go index 9c313d4..addd510 100644 --- a/internal/store/connection.go +++ b/internal/store/connection.go @@ -63,6 +63,8 @@ func getFromConfigSSH() (string, error) { func GetConnections() (*connect.Connections, error) { var connections connect.Connections + //todo add fast read ~goroutine + cls, err := getFromLocalStorage() if err != nil { logger.Error(err.Error()) From 722ee0771a0811e441a7e5f02a0447b1ecc2c8de Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 11 Nov 2025 14:19:41 +0300 Subject: [PATCH 10/88] feat: test commit --- pkg/connect/ssh_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connect/ssh_config.go b/pkg/connect/ssh_config.go index 75ef1a4..db5a117 100644 --- a/pkg/connect/ssh_config.go +++ b/pkg/connect/ssh_config.go @@ -1,6 +1,6 @@ package connect -// FromConfig todo add logic +// FromConfig todo add logic test func FromConfig(config string) (*Connections, error) { return new(Connections), nil } From aea83b83f2183062f9d25c4ff32bc5ad34f6d523 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 11 Nov 2025 14:20:01 +0300 Subject: [PATCH 11/88] fix: delet elogic test commit --- pkg/connect/ssh_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connect/ssh_config.go b/pkg/connect/ssh_config.go index db5a117..75ef1a4 100644 --- a/pkg/connect/ssh_config.go +++ b/pkg/connect/ssh_config.go @@ -1,6 +1,6 @@ package connect -// FromConfig todo add logic test +// FromConfig todo add logic func FromConfig(config string) (*Connections, error) { return new(Connections), nil } From 0a5c46249751b7f8f84b2f1d001cd43c22069327 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:47:23 +0300 Subject: [PATCH 12/88] feat: delete extra files --- internal/storage/helper.go | 53 ----------- internal/storage/helper_test.go | 157 -------------------------------- 2 files changed, 210 deletions(-) delete mode 100644 internal/storage/helper.go delete mode 100644 internal/storage/helper_test.go diff --git a/internal/storage/helper.go b/internal/storage/helper.go deleted file mode 100644 index 525afc9..0000000 --- a/internal/storage/helper.go +++ /dev/null @@ -1,53 +0,0 @@ -package storage - -import ( - "github.com/misha-ssh/kernel/configs/envconst" - "github.com/misha-ssh/kernel/configs/envname" - "os" - "os/user" - "path/filepath" -) - -const CharHidden = "." - -// GetAppDir get dir application -func GetAppDir() string { - usr, err := user.Current() - if err != nil { - panic(err) - } - - hiddenDir := CharHidden + envconst.AppName - - if os.Getenv(envname.Testing) == envconst.IsTesting { - return filepath.Join(os.TempDir(), hiddenDir) - } - - return filepath.Join(usr.HomeDir, hiddenDir) -} - -// GetDirSSH get dir ssh -func GetDirSSH() string { - homeDir, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - return filepath.Join(homeDir, envconst.DirectionsUserPrivateKey) -} - -// GetPrivateKeysDir get dir where save private keys -func GetPrivateKeysDir() string { - return filepath.Join(GetAppDir(), envconst.DirectionPrivateKeys) -} - -// GetDirectionAndFilename get dir and filename from full path -func GetDirectionAndFilename(fullPath string) (string, string) { - return filepath.Dir(fullPath), - filepath.Base(fullPath) -} - -// GetFullPath get full path from dir and filename -func GetFullPath(direction string, filename string) string { - return filepath.Join(direction, filename) -} diff --git a/internal/storage/helper_test.go b/internal/storage/helper_test.go deleted file mode 100644 index 002f9a8..0000000 --- a/internal/storage/helper_test.go +++ /dev/null @@ -1,157 +0,0 @@ -//go:build unit - -package storage - -import ( - "os" - "os/user" - "path/filepath" - "testing" - - "github.com/misha-ssh/kernel/configs/envconst" - "github.com/misha-ssh/kernel/configs/envname" - "github.com/stretchr/testify/require" -) - -func TestGetAppDir(t *testing.T) { - originalTesting := os.Getenv(envname.Testing) - defer func() { - require.NoError(t, os.Setenv(envname.Testing, originalTesting)) - }() - - tests := []struct { - name string - want func() string - isSetTesting bool - }{ - { - name: "success - get app dir", - want: func() string { - usr, err := user.Current() - require.NoError(t, err) - - return filepath.Join(usr.HomeDir, CharHidden+envconst.AppName) - }, - isSetTesting: false, - }, - { - name: "success - get test app dir", - want: func() string { - return filepath.Join(os.TempDir(), CharHidden+envconst.AppName) - }, - isSetTesting: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.isSetTesting { - require.NoError(t, os.Setenv(envname.Testing, "true")) - } else { - require.NoError(t, os.Setenv(envname.Testing, "false")) - } - - require.Equal(t, GetAppDir(), tt.want()) - }) - } -} - -func TestGetDirectionAndFilename(t *testing.T) { - tests := []struct { - name string - fullPath string - wantDir string - wantFile string - }{ - { - name: "simple path", - fullPath: "/home/user/file.txt", - wantDir: "/home/user", - wantFile: "file.txt", - }, - { - name: "nested path", - fullPath: "/home/user/documents/file.txt", - wantDir: "/home/user/documents", - wantFile: "file.txt", - }, - { - name: "current dir file", - fullPath: "file.txt", - wantDir: ".", - wantFile: "file.txt", - }, - { - name: "empty path", - fullPath: "", - wantDir: ".", - wantFile: ".", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotDir, gotFile := GetDirectionAndFilename(tt.fullPath) - require.Equal(t, gotDir, tt.wantDir) - require.Equal(t, gotFile, tt.wantFile) - }) - } -} - -func TestGetFullPath(t *testing.T) { - tests := []struct { - name string - direction string - filename string - want string - }{ - { - name: "simple join", - direction: "/home/user", - filename: "file.txt", - want: "/home/user/file.txt", - }, - { - name: "empty dir", - direction: "", - filename: "file.txt", - want: "file.txt", - }, - { - name: "empty filename", - direction: "/home/user", - filename: "", - want: "/home/user", - }, - { - name: "both empty", - direction: "", - filename: "", - want: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, GetFullPath(tt.direction, tt.filename), tt.want) - }) - } -} - -func TestGetPrivateKeysDir(t *testing.T) { - tests := []struct { - name string - want func() string - }{ - { - name: "success - get private keys dir", - want: func() string { - return filepath.Join(GetAppDir(), envconst.DirectionPrivateKeys) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, GetPrivateKeysDir(), tt.want()) - }) - } -} From e0e9dc1f8ab2724465ad7eff94777ecba8578793 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:47:41 +0300 Subject: [PATCH 13/88] fix: update logic storage --- internal/storage/git.go | 4 + internal/storage/git_test.go | 1 + internal/storage/local.go | 99 +++++++++ internal/storage/local_test.go | 353 +++++++++++++++++++++++++++++++ internal/storage/s3.go | 4 + internal/storage/s3_test.go | 1 + internal/storage/storage.go | 103 ++++----- internal/storage/storage_test.go | 337 +++++++---------------------- 8 files changed, 573 insertions(+), 329 deletions(-) create mode 100644 internal/storage/git.go create mode 100644 internal/storage/git_test.go create mode 100644 internal/storage/local.go create mode 100644 internal/storage/local_test.go create mode 100644 internal/storage/s3.go create mode 100644 internal/storage/s3_test.go diff --git a/internal/storage/git.go b/internal/storage/git.go new file mode 100644 index 0000000..6c98bb5 --- /dev/null +++ b/internal/storage/git.go @@ -0,0 +1,4 @@ +package storage + +// Git todo add logic +type Git struct{} diff --git a/internal/storage/git_test.go b/internal/storage/git_test.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/internal/storage/git_test.go @@ -0,0 +1 @@ +package storage diff --git a/internal/storage/local.go b/internal/storage/local.go new file mode 100644 index 0000000..5e65bcc --- /dev/null +++ b/internal/storage/local.go @@ -0,0 +1,99 @@ +package storage + +import ( + "errors" + "os" + "path/filepath" + "strings" +) + +type Local struct{} + +var ErrEmptyDirectory = errors.New("empty directory") +var ErrDeleteDirectory = errors.New("get dir, delete only file") + +func NewLocal() *Local { + return new(Local) +} + +// Create creates a new file at the specified path, including parent directories if needed. +// Returns error if file creation fails. +func (l *Local) Create(path string, filename string) error { + if strings.TrimSpace(path) == "" { + return ErrEmptyDirectory + } + + if _, err := os.Stat(path); os.IsNotExist(err) { + err = os.Mkdir(path, os.ModePerm) + if err != nil { + return err + } + } + + if strings.TrimSpace(filename) != "" { + return l.Write(path, filename, "") + } + + return nil +} + +// Delete removes the specified file. Returns error if deletion fails. +func (l *Local) Delete(path string, filename string) error { + file := filepath.Join(path, filename) + info, err := os.Stat(file) + if err != nil { + return err + } + + if info.IsDir() { + return ErrDeleteDirectory + } + + return os.Remove(filepath.Join(path, filename)) +} + +// Exists checks if a file exists at the given path and is not a directory. +// Returns boolean indicating existence. +func (l *Local) Exists(path string, filename string) bool { + info, err := os.Stat(filepath.Join(path, filename)) + if errors.Is(err, os.ErrNotExist) { + return false + } + + return !info.IsDir() +} + +// Get reads and returns the contents of a file as a string. +// Returns error if file cannot be read. +func (l *Local) Get(path string, filename string) (string, error) { + data, err := os.ReadFile(filepath.Join(path, filename)) + if err != nil { + return "", err + } + + return string(data), nil +} + +// Write saves data to a file, overwriting existing content. +// Creates file if it doesn't exist. Returns error on failure. +func (l *Local) Write(path string, filename string, data string) error { + err := os.WriteFile(filepath.Join(path, filename), []byte(data), os.ModePerm) + if err != nil { + return err + } + + return nil +} + +// GetOpenFile opens a file with specified flags (os.O_RDWR, etc.) and returns the file handle. +// Returns error if file cannot be opened. +func (l *Local) GetOpenFile(path string, filename string, flags int) (*os.File, error) { + file := filepath.Join(path, filename) + + openFile, err := os.OpenFile(file, flags, os.ModePerm) + if err != nil { + return nil, err + } + + return openFile, nil +} diff --git a/internal/storage/local_test.go b/internal/storage/local_test.go new file mode 100644 index 0000000..9960883 --- /dev/null +++ b/internal/storage/local_test.go @@ -0,0 +1,353 @@ +package storage + +import ( + "bytes" + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "testing" +) + +func TestCreate(t *testing.T) { + type args struct { + direction string + filename string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "success - create file", + args: args{ + direction: t.TempDir(), + filename: "test.txt", + }, + wantErr: false, + }, + { + name: "success - create dir", + args: args{ + direction: t.TempDir() + "/new_dir", + filename: "", + }, + wantErr: false, + }, + { + name: "fail - empty dir", + args: args{ + direction: "", + filename: "new.txt", + }, + wantErr: true, + }, + { + name: "fail - empty dir and filename", + args: args{ + direction: "", + filename: "", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + err := localStorage.Create(tt.args.direction, tt.args.filename) + require.Equal(t, tt.wantErr, err != nil) + }) + } +} + +func TestDelete(t *testing.T) { + type args struct { + direction string + filename string + } + tests := []struct { + name string + args args + isCreateFile bool + wantErr bool + }{ + { + name: "success - delete file", + args: args{ + direction: t.TempDir(), + filename: "test.txt", + }, + isCreateFile: true, + wantErr: false, + }, + { + name: "fail - delete non exists file", + args: args{ + direction: t.TempDir(), + filename: "nonexistent.txt", + }, + isCreateFile: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + if tt.isCreateFile { + require.NoError(t, localStorage.Create(tt.args.direction, tt.args.filename)) + } + + err := localStorage.Delete(tt.args.direction, tt.args.filename) + require.Equal(t, tt.wantErr, err != nil) + }) + } +} + +func TestExists(t *testing.T) { + tempDir := t.TempDir() + + type args struct { + direction string + filename string + } + tests := []struct { + name string + args args + isCreateFile bool + want bool + }{ + { + name: "success - is exists", + args: args{ + direction: tempDir, + filename: "test.txt", + }, + isCreateFile: true, + want: true, + }, + { + name: "fail - is not exists", + args: args{ + direction: tempDir, + filename: "nonexistent.txt", + }, + isCreateFile: false, + want: false, + }, + { + name: "fail - is not exists empty file", + args: args{ + direction: tempDir, + filename: "", + }, + isCreateFile: false, + want: false, + }, + { + name: "fail - is not exists empty dir", + args: args{ + direction: "", + filename: "text.txt", + }, + isCreateFile: false, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + if tt.isCreateFile { + require.NoError(t, localStorage.Create(tt.args.direction, tt.args.filename)) + } + + require.Equal(t, localStorage.Exists(tt.args.direction, tt.args.filename), tt.want) + }) + } +} + +func TestGet(t *testing.T) { + tempDir := t.TempDir() + + testFiles := map[string][]byte{ + "test.txt": []byte("test data"), + "empty.txt": []byte(""), + "large.txt": make([]byte, 1024*1024), + "large-repeat.txt": bytes.Repeat([]byte("x"), 1024*1024), + } + + for filename, data := range testFiles { + filePath := filepath.Join(tempDir, filename) + require.NoError(t, os.WriteFile(filePath, data, 0644)) + } + + tests := []struct { + name string + direction string + filename string + want string + wantErr bool + }{ + { + name: "success - read test file", + direction: tempDir, + filename: "test.txt", + want: string(testFiles["test.txt"]), + wantErr: false, + }, + { + name: "success - read empty file", + direction: tempDir, + filename: "empty.txt", + want: string(testFiles["empty.txt"]), + wantErr: false, + }, + { + name: "success - read large file", + direction: tempDir, + filename: "large.txt", + want: string(testFiles["large.txt"]), + wantErr: false, + }, + { + name: "success - read large file", + direction: tempDir, + filename: "large-repeat.txt", + want: string(testFiles["large-repeat.txt"]), + wantErr: false, + }, + { + name: "fail - non-existent file", + direction: tempDir, + filename: "nonexistent.txt", + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + got, err := localStorage.Get(tt.direction, tt.filename) + require.Equal(t, tt.wantErr, err != nil) + require.Equal(t, got, tt.want) + }) + } +} + +func TestWrite(t *testing.T) { + type args struct { + direction string + filename string + data string + } + tests := []struct { + name string + args args + wantErr bool + want string + }{ + { + name: "write to new file", + args: args{ + direction: t.TempDir(), + filename: "test.txt", + data: "Hello, World!", + }, + wantErr: false, + want: "Hello, World!", + }, + { + name: "write to existing file (overwrite)", + args: args{ + direction: t.TempDir(), + filename: "test.txt", + data: "New content", + }, + wantErr: false, + want: "New content", + }, + { + name: "write empty data to new file", + args: args{ + direction: t.TempDir(), + filename: "empty.txt", + data: "", + }, + wantErr: false, + want: "", + }, + { + name: "write to invalid filename", + args: args{ + direction: t.TempDir(), + filename: "", + data: "Invalid", + }, + wantErr: true, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + err := localStorage.Write(tt.args.direction, tt.args.filename, tt.args.data) + require.Equal(t, tt.wantErr, err != nil) + + got, err := localStorage.Get(tt.args.direction, tt.args.filename) + require.Equal(t, tt.wantErr, err != nil) + require.Equal(t, got, tt.want) + }) + } +} + +func TestGetOpenFile(t *testing.T) { + type args struct { + direction string + filename string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "write to new file", + args: args{ + direction: t.TempDir(), + filename: "test.txt", + }, + wantErr: false, + }, + { + name: "error on invalid directory", + args: args{ + direction: "invalidDir" + t.TempDir(), + filename: "test.txt", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + localStorage := new(Local) + + flags := os.O_WRONLY | os.O_APPEND | os.O_CREATE + got, err := localStorage.GetOpenFile(tt.args.direction, tt.args.filename, flags) + require.Equal(t, tt.wantErr, err != nil) + + _, err = got.Write([]byte("test")) + require.Equal(t, tt.wantErr, err != nil) + + err = got.Close() + require.Equal(t, tt.wantErr, err != nil) + + fileIsExists := localStorage.Exists(tt.args.direction, tt.args.filename) + require.Equal(t, fileIsExists, !tt.wantErr) + }) + } +} diff --git a/internal/storage/s3.go b/internal/storage/s3.go new file mode 100644 index 0000000..1ff4769 --- /dev/null +++ b/internal/storage/s3.go @@ -0,0 +1,4 @@ +package storage + +// S3 todo add logic +type S3 struct{} diff --git a/internal/storage/s3_test.go b/internal/storage/s3_test.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/internal/storage/s3_test.go @@ -0,0 +1 @@ +package storage diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 023455e..98662d5 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,93 +1,62 @@ package storage import ( - "errors" + "github.com/misha-ssh/kernel/configs/envconst" + "github.com/misha-ssh/kernel/configs/envname" "os" + "os/user" "path/filepath" - "strings" ) -var ErrEmptyDirectory = errors.New("empty directory") -var ErrDeleteDirectory = errors.New("get dir, delete only file") - -// Create creates a new file at the specified path, including parent directories if needed. -// Returns error if file creation fails. -func Create(path string, filename string) error { - if strings.TrimSpace(path) == "" { - return ErrEmptyDirectory - } - - if _, err := os.Stat(path); os.IsNotExist(err) { - err = os.Mkdir(path, os.ModePerm) - if err != nil { - return err - } - } - - if strings.TrimSpace(filename) != "" { - return Write(path, filename, "") - } - - return nil +type Storage interface { + Create(path string, filename string) error + Delete(path string, filename string) error + Exists(path string, filename string) bool + Get(path string, filename string) (string, error) + Write(path string, filename string, data string) error + GetOpenFile(path string, filename string, flags int) (*os.File, error) } -// Delete removes the specified file. Returns error if deletion fails. -func Delete(path string, filename string) error { - file := filepath.Join(path, filename) - info, err := os.Stat(file) - if err != nil { - return err - } +const CharHidden = "." - if info.IsDir() { - return ErrDeleteDirectory +// GetAppDir get dir application +func GetAppDir() string { + usr, err := user.Current() + if err != nil { + panic(err) } - return os.Remove(filepath.Join(path, filename)) -} + hiddenDir := CharHidden + envconst.AppName -// Exists checks if a file exists at the given path and is not a directory. -// Returns boolean indicating existence. -func Exists(path string, filename string) bool { - info, err := os.Stat(filepath.Join(path, filename)) - if errors.Is(err, os.ErrNotExist) { - return false + if os.Getenv(envname.Testing) == envconst.IsTesting { + return filepath.Join(os.TempDir(), hiddenDir) } - return !info.IsDir() + return filepath.Join(usr.HomeDir, hiddenDir) } -// Get reads and returns the contents of a file as a string. -// Returns error if file cannot be read. -func Get(path string, filename string) (string, error) { - data, err := os.ReadFile(filepath.Join(path, filename)) +// GetDirSSH get dir ssh +func GetDirSSH() string { + homeDir, err := os.UserHomeDir() if err != nil { - return "", err + panic(err) } - return string(data), nil + return filepath.Join(homeDir, envconst.DirectionsUserPrivateKey) } -// Write saves data to a file, overwriting existing content. -// Creates file if it doesn't exist. Returns error on failure. -func Write(path string, filename string, data string) error { - err := os.WriteFile(filepath.Join(path, filename), []byte(data), os.ModePerm) - if err != nil { - return err - } - - return nil +// GetPrivateKeysDir get dir where save private keys +func GetPrivateKeysDir() string { + return filepath.Join(GetAppDir(), envconst.DirectionPrivateKeys) } -// GetOpenFile opens a file with specified flags (os.O_RDWR, etc.) and returns the file handle. -// Returns error if file cannot be opened. -func GetOpenFile(path string, filename string, flags int) (*os.File, error) { - file := filepath.Join(path, filename) - - openFile, err := os.OpenFile(file, flags, os.ModePerm) - if err != nil { - return nil, err - } +// GetDirectionAndFilename get dir and filename from full path +func GetDirectionAndFilename(fullPath string) (string, string) { + return filepath.Dir(fullPath), + filepath.Base(fullPath) +} - return openFile, nil +// GetFullPath get full path from dir and filename +func GetFullPath(direction string, filename string) string { + return filepath.Join(direction, filename) } diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go index fa3bdee..4db59dc 100644 --- a/internal/storage/storage_test.go +++ b/internal/storage/storage_test.go @@ -3,342 +3,155 @@ package storage import ( - "bytes" + "github.com/misha-ssh/kernel/configs/envconst" + "github.com/misha-ssh/kernel/configs/envname" "os" + "os/user" "path/filepath" "testing" "github.com/stretchr/testify/require" ) -func TestCreate(t *testing.T) { - type args struct { - direction string - filename string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "success - create file", - args: args{ - direction: t.TempDir(), - filename: "test.txt", - }, - wantErr: false, - }, - { - name: "success - create dir", - args: args{ - direction: t.TempDir() + "/new_dir", - filename: "", - }, - wantErr: false, - }, - { - name: "fail - empty dir", - args: args{ - direction: "", - filename: "new.txt", - }, - wantErr: true, - }, - { - name: "fail - empty dir and filename", - args: args{ - direction: "", - filename: "", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := Create(tt.args.direction, tt.args.filename) - require.Equal(t, tt.wantErr, err != nil) - }) - } -} +func TestGetAppDir(t *testing.T) { + originalTesting := os.Getenv(envname.Testing) + defer func() { + require.NoError(t, os.Setenv(envname.Testing, originalTesting)) + }() -func TestDelete(t *testing.T) { - type args struct { - direction string - filename string - } tests := []struct { name string - args args - isCreateFile bool - wantErr bool + want func() string + isSetTesting bool }{ { - name: "success - delete file", - args: args{ - direction: t.TempDir(), - filename: "test.txt", + name: "success - get app dir", + want: func() string { + usr, err := user.Current() + require.NoError(t, err) + + return filepath.Join(usr.HomeDir, CharHidden+envconst.AppName) }, - isCreateFile: true, - wantErr: false, + isSetTesting: false, }, { - name: "fail - delete non exists file", - args: args{ - direction: t.TempDir(), - filename: "nonexistent.txt", + name: "success - get test app dir", + want: func() string { + return filepath.Join(os.TempDir(), CharHidden+envconst.AppName) }, - isCreateFile: false, - wantErr: true, + isSetTesting: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.isCreateFile { - require.NoError(t, Create(tt.args.direction, tt.args.filename)) + if tt.isSetTesting { + require.NoError(t, os.Setenv(envname.Testing, "true")) + } else { + require.NoError(t, os.Setenv(envname.Testing, "false")) } - err := Delete(tt.args.direction, tt.args.filename) - require.Equal(t, tt.wantErr, err != nil) + require.Equal(t, GetAppDir(), tt.want()) }) } } -func TestExists(t *testing.T) { - tempDir := t.TempDir() - - type args struct { - direction string - filename string - } +func TestGetDirectionAndFilename(t *testing.T) { tests := []struct { - name string - args args - isCreateFile bool - want bool + name string + fullPath string + wantDir string + wantFile string }{ { - name: "success - is exists", - args: args{ - direction: tempDir, - filename: "test.txt", - }, - isCreateFile: true, - want: true, + name: "simple path", + fullPath: "/home/user/file.txt", + wantDir: "/home/user", + wantFile: "file.txt", }, { - name: "fail - is not exists", - args: args{ - direction: tempDir, - filename: "nonexistent.txt", - }, - isCreateFile: false, - want: false, + name: "nested path", + fullPath: "/home/user/documents/file.txt", + wantDir: "/home/user/documents", + wantFile: "file.txt", }, { - name: "fail - is not exists empty file", - args: args{ - direction: tempDir, - filename: "", - }, - isCreateFile: false, - want: false, + name: "current dir file", + fullPath: "file.txt", + wantDir: ".", + wantFile: "file.txt", }, { - name: "fail - is not exists empty dir", - args: args{ - direction: "", - filename: "text.txt", - }, - isCreateFile: false, - want: false, + name: "empty path", + fullPath: "", + wantDir: ".", + wantFile: ".", }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.isCreateFile { - require.NoError(t, Create(tt.args.direction, tt.args.filename)) - } - - require.Equal(t, Exists(tt.args.direction, tt.args.filename), tt.want) + gotDir, gotFile := GetDirectionAndFilename(tt.fullPath) + require.Equal(t, gotDir, tt.wantDir) + require.Equal(t, gotFile, tt.wantFile) }) } } -func TestGet(t *testing.T) { - tempDir := t.TempDir() - - testFiles := map[string][]byte{ - "test.txt": []byte("test data"), - "empty.txt": []byte(""), - "large.txt": make([]byte, 1024*1024), - "large-repeat.txt": bytes.Repeat([]byte("x"), 1024*1024), - } - - for filename, data := range testFiles { - filePath := filepath.Join(tempDir, filename) - require.NoError(t, os.WriteFile(filePath, data, 0644)) - } - +func TestGetFullPath(t *testing.T) { tests := []struct { name string direction string filename string want string - wantErr bool }{ { - name: "success - read test file", - direction: tempDir, - filename: "test.txt", - want: string(testFiles["test.txt"]), - wantErr: false, - }, - { - name: "success - read empty file", - direction: tempDir, - filename: "empty.txt", - want: string(testFiles["empty.txt"]), - wantErr: false, + name: "simple join", + direction: "/home/user", + filename: "file.txt", + want: "/home/user/file.txt", }, { - name: "success - read large file", - direction: tempDir, - filename: "large.txt", - want: string(testFiles["large.txt"]), - wantErr: false, + name: "empty dir", + direction: "", + filename: "file.txt", + want: "file.txt", }, { - name: "success - read large file", - direction: tempDir, - filename: "large-repeat.txt", - want: string(testFiles["large-repeat.txt"]), - wantErr: false, + name: "empty filename", + direction: "/home/user", + filename: "", + want: "/home/user", }, { - name: "fail - non-existent file", - direction: tempDir, - filename: "nonexistent.txt", + name: "both empty", + direction: "", + filename: "", want: "", - wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Get(tt.direction, tt.filename) - require.Equal(t, tt.wantErr, err != nil) - require.Equal(t, got, tt.want) - }) - } -} - -func TestWrite(t *testing.T) { - type args struct { - direction string - filename string - data string - } - tests := []struct { - name string - args args - wantErr bool - want string - }{ - { - name: "write to new file", - args: args{ - direction: t.TempDir(), - filename: "test.txt", - data: "Hello, World!", - }, - wantErr: false, - want: "Hello, World!", - }, - { - name: "write to existing file (overwrite)", - args: args{ - direction: t.TempDir(), - filename: "test.txt", - data: "New content", - }, - wantErr: false, - want: "New content", - }, - { - name: "write empty data to new file", - args: args{ - direction: t.TempDir(), - filename: "empty.txt", - data: "", - }, - wantErr: false, - want: "", - }, - { - name: "write to invalid filename", - args: args{ - direction: t.TempDir(), - filename: "", - data: "Invalid", - }, - wantErr: true, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := Write(tt.args.direction, tt.args.filename, tt.args.data) - require.Equal(t, tt.wantErr, err != nil) - - got, err := Get(tt.args.direction, tt.args.filename) - require.Equal(t, tt.wantErr, err != nil) - require.Equal(t, got, tt.want) + require.Equal(t, GetFullPath(tt.direction, tt.filename), tt.want) }) } } -func TestGetOpenFile(t *testing.T) { - type args struct { - direction string - filename string - } +func TestGetPrivateKeysDir(t *testing.T) { tests := []struct { - name string - args args - wantErr bool + name string + want func() string }{ { - name: "write to new file", - args: args{ - direction: t.TempDir(), - filename: "test.txt", - }, - wantErr: false, - }, - { - name: "error on invalid directory", - args: args{ - direction: "invalidDir" + t.TempDir(), - filename: "test.txt", + name: "success - get private keys dir", + want: func() string { + return filepath.Join(GetAppDir(), envconst.DirectionPrivateKeys) }, - wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - flags := os.O_WRONLY | os.O_APPEND | os.O_CREATE - got, err := GetOpenFile(tt.args.direction, tt.args.filename, flags) - require.Equal(t, tt.wantErr, err != nil) - - _, err = got.Write([]byte("test")) - require.Equal(t, tt.wantErr, err != nil) - - err = got.Close() - require.Equal(t, tt.wantErr, err != nil) - - fileIsExists := Exists(tt.args.direction, tt.args.filename) - require.Equal(t, fileIsExists, !tt.wantErr) + require.Equal(t, GetPrivateKeysDir(), tt.want()) }) } } From 4f2fa5d2b8668e9cbd822890e091167b9d944582 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:48:01 +0300 Subject: [PATCH 14/88] feat: add new config name --- configs/envname/name.go | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/envname/name.go b/configs/envname/name.go index 6a8051c..f4c7e39 100644 --- a/configs/envname/name.go +++ b/configs/envname/name.go @@ -4,4 +4,5 @@ const ( Theme = "THEME" Logger = "LOGGER" Testing = "GO_TESTING" + Storage = "STORAGE" ) From 07d12e59ed4bbabc5df3681a71ecf58f5302a28d Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:48:22 +0300 Subject: [PATCH 15/88] feat: add base logic space struct --- internal/space/space.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 internal/space/space.go diff --git a/internal/space/space.go b/internal/space/space.go new file mode 100644 index 0000000..1b4c50d --- /dev/null +++ b/internal/space/space.go @@ -0,0 +1,40 @@ +package space + +import ( + "github.com/misha-ssh/kernel/configs/envconst" + "github.com/misha-ssh/kernel/configs/envname" + "github.com/misha-ssh/kernel/internal/config" + "github.com/misha-ssh/kernel/internal/storage" + "github.com/misha-ssh/kernel/pkg/connect" +) + +type Space struct { + Storage *storage.Storage +} + +func New() *Space { + space := &Space{} + + switch config.Get(envname.Storage) { + case envconst.TypeLocalStorage: + space.Storage = storage.NewLocal() + } + + return space +} + +func (s *Space) GetConnections() (*connect.Connections, error) { + return nil, nil +} + +func (s *Space) SaveConnection(connection *connect.Connect) error { + return nil +} + +func (s *Space) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { + return nil, nil +} + +func (s *Space) DeleteConnection(connection *connect.Connect) error { + return nil +} From 6fcc31193d661b2d3505c464da022a284012a118 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:48:39 +0300 Subject: [PATCH 16/88] feat: add const type storage --- configs/envconst/storage.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 configs/envconst/storage.go diff --git a/configs/envconst/storage.go b/configs/envconst/storage.go new file mode 100644 index 0000000..aa0960d --- /dev/null +++ b/configs/envconst/storage.go @@ -0,0 +1,7 @@ +package envconst + +const ( + TypeLocalStorage = "local" + TypeGitStorage = "git" + TypeS3Storage = "s3" +) From 1deb8ba60bcef399b14eb50b45e9f6958c848b9c Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 14 Nov 2025 21:49:30 +0300 Subject: [PATCH 17/88] feat: add maybe files --- internal/storage/ssh_config.go | 4 ++++ internal/storage/ssh_config_test.go | 1 + 2 files changed, 5 insertions(+) create mode 100644 internal/storage/ssh_config.go create mode 100644 internal/storage/ssh_config_test.go diff --git a/internal/storage/ssh_config.go b/internal/storage/ssh_config.go new file mode 100644 index 0000000..ba97fca --- /dev/null +++ b/internal/storage/ssh_config.go @@ -0,0 +1,4 @@ +package storage + +// SSHConfig todo add logic or must be implemented in another package +type SSHConfig struct{} diff --git a/internal/storage/ssh_config_test.go b/internal/storage/ssh_config_test.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/internal/storage/ssh_config_test.go @@ -0,0 +1 @@ +package storage From 35f59e18392509e8eee3fb3e8f10a6cd877219b4 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 02:35:47 +0300 Subject: [PATCH 18/88] fix: delete extra link on interface --- internal/space/space.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/space/space.go b/internal/space/space.go index 1b4c50d..8917a80 100644 --- a/internal/space/space.go +++ b/internal/space/space.go @@ -9,7 +9,7 @@ import ( ) type Space struct { - Storage *storage.Storage + Storage storage.Storage } func New() *Space { From 8e16b2a087f203f5b6a1ab1e6f041c8d24677116 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 02:39:08 +0300 Subject: [PATCH 19/88] feat: add default logic --- internal/space/space.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/space/space.go b/internal/space/space.go index 8917a80..7c5d0df 100644 --- a/internal/space/space.go +++ b/internal/space/space.go @@ -1,6 +1,7 @@ package space import ( + "fmt" "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/configs/envname" "github.com/misha-ssh/kernel/internal/config" @@ -18,6 +19,8 @@ func New() *Space { switch config.Get(envname.Storage) { case envconst.TypeLocalStorage: space.Storage = storage.NewLocal() + default: + panic(fmt.Sprintf("%s: unknown variable type", envname.Storage)) } return space From 8dc2817a8dd736d2328b0c02698ea82ddb319967 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 15:56:00 +0300 Subject: [PATCH 20/88] fix: put validate private key in connection pkg --- internal/store/private_key.go | 38 +---------------------------------- pkg/connect/validate.go | 27 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/internal/store/private_key.go b/internal/store/private_key.go index 804d3c6..2ed1434 100644 --- a/internal/store/private_key.go +++ b/internal/store/private_key.go @@ -1,15 +1,11 @@ package store import ( - "encoding/pem" "errors" - "reflect" - "strings" - "github.com/misha-ssh/kernel/internal/logger" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" - "golang.org/x/crypto/ssh" + "reflect" ) var ( @@ -17,29 +13,9 @@ var ( ErrWriteToFilePrivateKey = errors.New("err write to file private key") ErrCreateFilePrivateKey = errors.New("err create file private key") - ErrNotValidPrivateKey = errors.New("private key is not valid") ErrGetDataPrivateKey = errors.New("private key get data error") ) -func validatePrivateKey(privateKey string, passphrase string) error { - block, _ := pem.Decode([]byte(privateKey)) - if block == nil { - return ErrNotValidPrivateKey - } - - _, err := ssh.ParseRawPrivateKey([]byte(privateKey)) - if err != nil { - if !strings.Contains(err.Error(), "passphrase") { - logger.Error(err.Error()) - return err - } - - _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) - } - - return err -} - // SavePrivateKey create private key for connection in spec dir func SavePrivateKey(connection *connect.Connect) (string, error) { direction, filename := storage.GetDirectionAndFilename(connection.SshOptions.PrivateKey) @@ -49,12 +25,6 @@ func SavePrivateKey(connection *connect.Connect) (string, error) { return "", ErrGetDataPrivateKey } - err = validatePrivateKey(dataPrivateKey, connection.SshOptions.Passphrase) - if err != nil { - logger.Error(err.Error()) - return "", err - } - filenamePrivateKey := connection.Alias err = storage.Create(DirectionKeys, filenamePrivateKey) @@ -107,12 +77,6 @@ func UpdatePrivateKey(connection *connect.Connect) (string, error) { return "", ErrGetDataPrivateKey } - err = validatePrivateKey(dataPrivateKey, connection.SshOptions.Passphrase) - if err != nil { - logger.Error(err.Error()) - return "", err - } - if !reflect.DeepEqual(existDataPrivateKey, dataPrivateKey) { err = DeletePrivateKey(connection) if err != nil { diff --git a/pkg/connect/validate.go b/pkg/connect/validate.go index 88d41e1..565cbe3 100644 --- a/pkg/connect/validate.go +++ b/pkg/connect/validate.go @@ -1,7 +1,10 @@ package connect import ( + "encoding/pem" "errors" + "github.com/misha-ssh/kernel/internal/logger" + "golang.org/x/crypto/ssh" "net" "regexp" "strings" @@ -17,6 +20,7 @@ var ( func (c *Connect) Validate() error { for _, err := range []error{ validateAlias(c.Alias), + validatePrivateKey(c.SshOptions.PrivateKey, c.SshOptions.Passphrase, c.Password), validatePassword(c.Password, c.SshOptions.PrivateKey), validateLogin(c.Login), validateAddress(c.Address), @@ -99,6 +103,29 @@ func validatePassword(password string, privateKey string) error { return nil } +func validatePrivateKey(privateKey string, passphrase string, password string) error { + if strings.TrimSpace(password) != "" { + return nil + } + + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return errors.New("private key is not valid") + } + + _, err := ssh.ParseRawPrivateKey([]byte(privateKey)) + if err != nil { + if !strings.Contains(err.Error(), "passphrase") { + logger.Error(err.Error()) + return err + } + + _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) + } + + return err +} + func validateCreatedAt(date string) error { return validateDate(date) } From 64ea0167c0d2a2541f0239fa6593952918e02000 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 15:56:09 +0300 Subject: [PATCH 21/88] fix: delete extra code --- internal/store/connection.go | 41 +++++++----------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/internal/store/connection.go b/internal/store/connection.go index addd510..6a5d2d8 100644 --- a/internal/store/connection.go +++ b/internal/store/connection.go @@ -12,11 +12,9 @@ import ( ) const FileConnections = envconst.FilenameConnections -const FileConfigSSH = envconst.FilenameConfigSSH var ( DirectionApp = storage.GetAppDir() - DirectionSSH = storage.GetDirSSH() ErrEncryptData = errors.New("err encrypt data") ErrMarshalJson = errors.New("failed to marshal json") @@ -26,52 +24,29 @@ var ( ErrDecryptData = errors.New("failed to decrypt data") ) -func getFromLocalStorage() (string, error) { +// GetConnections get connection from file +func GetConnections() (*connect.Connections, error) { + var connections connect.Connections + cryptKey, err := GetCryptKey() if err != nil { logger.Error(err.Error()) - return "", err + return nil, err } encryptedConnections, err := storage.Get(DirectionApp, FileConnections) if err != nil { logger.Error(err.Error()) - return "", ErrGetConnection + return nil, ErrGetConnection } decryptedConnections, err := crypto.Decrypt(encryptedConnections, cryptKey) if err != nil { logger.Error(err.Error()) - return "", ErrDecryptData - } - - return decryptedConnections, nil -} - -// todo add logic get connection from ssh config -func getFromConfigSSH() (string, error) { - sshConfig, err := storage.Get(DirectionSSH, FileConfigSSH) - if err != nil { - logger.Error(err.Error()) - return "", err - } - - return sshConfig, nil -} - -// GetConnections get connection from file -func GetConnections() (*connect.Connections, error) { - var connections connect.Connections - - //todo add fast read ~goroutine - - cls, err := getFromLocalStorage() - if err != nil { - logger.Error(err.Error()) - return nil, ErrGetConnection + return nil, ErrDecryptData } - err = json.Unmarshal([]byte(cls), &connections) + err = json.Unmarshal([]byte(decryptedConnections), &connections) if err != nil { logger.Error(err.Error()) return nil, ErrUnmarshalJson From c1e8d0d25797c7ea8250c5f6a5cc9648d75155cd Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:01:40 +0300 Subject: [PATCH 22/88] fix: add new var in config --- internal/setup/setup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/setup/setup.go b/internal/setup/setup.go index f2bf98e..408b288 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -94,6 +94,7 @@ func initFileConfig() error { defaultValues := map[string]string{ envname.Theme: envconst.Theme, envname.Logger: envconst.TypeStorageLogger, + envname.Space: envconst.TypeLocalSpace, } for key, value := range defaultValues { From 7f799ea9c4f00010eb8e2ff3e99875512a605f69 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:04:32 +0300 Subject: [PATCH 23/88] fix: delete extra files --- internal/storage/ssh_config.go | 4 ---- internal/storage/ssh_config_test.go | 1 - 2 files changed, 5 deletions(-) delete mode 100644 internal/storage/ssh_config.go delete mode 100644 internal/storage/ssh_config_test.go diff --git a/internal/storage/ssh_config.go b/internal/storage/ssh_config.go deleted file mode 100644 index ba97fca..0000000 --- a/internal/storage/ssh_config.go +++ /dev/null @@ -1,4 +0,0 @@ -package storage - -// SSHConfig todo add logic or must be implemented in another package -type SSHConfig struct{} diff --git a/internal/storage/ssh_config_test.go b/internal/storage/ssh_config_test.go deleted file mode 100644 index 82be054..0000000 --- a/internal/storage/ssh_config_test.go +++ /dev/null @@ -1 +0,0 @@ -package storage From 59f9f0985413e69b70d5e2e334a28c97bb0465ca Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:39:06 +0300 Subject: [PATCH 24/88] fix: delete extra files --- configs/envconst/storage.go | 7 ------- pkg/connect/ssh_config.go | 6 ------ 2 files changed, 13 deletions(-) delete mode 100644 configs/envconst/storage.go delete mode 100644 pkg/connect/ssh_config.go diff --git a/configs/envconst/storage.go b/configs/envconst/storage.go deleted file mode 100644 index aa0960d..0000000 --- a/configs/envconst/storage.go +++ /dev/null @@ -1,7 +0,0 @@ -package envconst - -const ( - TypeLocalStorage = "local" - TypeGitStorage = "git" - TypeS3Storage = "s3" -) diff --git a/pkg/connect/ssh_config.go b/pkg/connect/ssh_config.go deleted file mode 100644 index 75ef1a4..0000000 --- a/pkg/connect/ssh_config.go +++ /dev/null @@ -1,6 +0,0 @@ -package connect - -// FromConfig todo add logic -func FromConfig(config string) (*Connections, error) { - return new(Connections), nil -} From 2590934bf26832813c041976f551b011df230a28 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:39:26 +0300 Subject: [PATCH 25/88] feat: add struct for work with ssh config --- pkg/ssh/config.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pkg/ssh/config.go diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go new file mode 100644 index 0000000..9c4aa49 --- /dev/null +++ b/pkg/ssh/config.go @@ -0,0 +1,15 @@ +package ssh + +import ( + "github.com/misha-ssh/kernel/internal/storage" +) + +type Config struct { + Path string +} + +func NewConfig() *Config { + return &Config{ + Path: storage.GetDirSSH(), + } +} From d8f501a1173121311ca7cf76f0d516723a6cb349 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:39:35 +0300 Subject: [PATCH 26/88] fix: delete extra type --- pkg/connect/connect.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/connect/connect.go b/pkg/connect/connect.go index 5be7a0d..31bf01f 100644 --- a/pkg/connect/connect.go +++ b/pkg/connect/connect.go @@ -1,14 +1,10 @@ package connect type ConnectionType string -type FromType string const ( // TypeSSH type for ssh connection TypeSSH ConnectionType = "ssh" - - FromLocal FromType = "local" - FromConfigSSH FromType = "config-ssh" ) type Connections struct { @@ -25,9 +21,6 @@ type Connect struct { CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` - //todo it is saved when updating and creating - From FromType `json:"from"` - // Type specifies the connection protocol (e.g., "ssh") Type ConnectionType `json:"type"` From da2a5a472a70e0f502c52eab47182a7d3a37fdc8 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:39:42 +0300 Subject: [PATCH 27/88] feat: add params --- internal/storage/local.go | 40 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/internal/storage/local.go b/internal/storage/local.go index 5e65bcc..32441a7 100644 --- a/internal/storage/local.go +++ b/internal/storage/local.go @@ -7,39 +7,43 @@ import ( "strings" ) -type Local struct{} +type Local struct { + path string +} var ErrEmptyDirectory = errors.New("empty directory") var ErrDeleteDirectory = errors.New("get dir, delete only file") func NewLocal() *Local { - return new(Local) + return &Local{ + path: GetAppDir(), + } } // Create creates a new file at the specified path, including parent directories if needed. // Returns error if file creation fails. -func (l *Local) Create(path string, filename string) error { - if strings.TrimSpace(path) == "" { +func (l *Local) Create(filename string) error { + if strings.TrimSpace(l.path) == "" { return ErrEmptyDirectory } - if _, err := os.Stat(path); os.IsNotExist(err) { - err = os.Mkdir(path, os.ModePerm) + if _, err := os.Stat(l.path); os.IsNotExist(err) { + err = os.Mkdir(l.path, os.ModePerm) if err != nil { return err } } if strings.TrimSpace(filename) != "" { - return l.Write(path, filename, "") + return l.Write(filename, "") } return nil } // Delete removes the specified file. Returns error if deletion fails. -func (l *Local) Delete(path string, filename string) error { - file := filepath.Join(path, filename) +func (l *Local) Delete(filename string) error { + file := filepath.Join(l.path, filename) info, err := os.Stat(file) if err != nil { return err @@ -49,13 +53,13 @@ func (l *Local) Delete(path string, filename string) error { return ErrDeleteDirectory } - return os.Remove(filepath.Join(path, filename)) + return os.Remove(filepath.Join(l.path, filename)) } // Exists checks if a file exists at the given path and is not a directory. // Returns boolean indicating existence. -func (l *Local) Exists(path string, filename string) bool { - info, err := os.Stat(filepath.Join(path, filename)) +func (l *Local) Exists(filename string) bool { + info, err := os.Stat(filepath.Join(l.path, filename)) if errors.Is(err, os.ErrNotExist) { return false } @@ -65,8 +69,8 @@ func (l *Local) Exists(path string, filename string) bool { // Get reads and returns the contents of a file as a string. // Returns error if file cannot be read. -func (l *Local) Get(path string, filename string) (string, error) { - data, err := os.ReadFile(filepath.Join(path, filename)) +func (l *Local) Get(filename string) (string, error) { + data, err := os.ReadFile(filepath.Join(l.path, filename)) if err != nil { return "", err } @@ -76,8 +80,8 @@ func (l *Local) Get(path string, filename string) (string, error) { // Write saves data to a file, overwriting existing content. // Creates file if it doesn't exist. Returns error on failure. -func (l *Local) Write(path string, filename string, data string) error { - err := os.WriteFile(filepath.Join(path, filename), []byte(data), os.ModePerm) +func (l *Local) Write(filename string, data string) error { + err := os.WriteFile(filepath.Join(l.path, filename), []byte(data), os.ModePerm) if err != nil { return err } @@ -87,8 +91,8 @@ func (l *Local) Write(path string, filename string, data string) error { // GetOpenFile opens a file with specified flags (os.O_RDWR, etc.) and returns the file handle. // Returns error if file cannot be opened. -func (l *Local) GetOpenFile(path string, filename string, flags int) (*os.File, error) { - file := filepath.Join(path, filename) +func (l *Local) GetOpenFile(filename string, flags int) (*os.File, error) { + file := filepath.Join(l.path, filename) openFile, err := os.OpenFile(file, flags, os.ModePerm) if err != nil { From 35064f417cc8fa0c3f1af52c9223444f54ee5e3a Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:39:51 +0300 Subject: [PATCH 28/88] fix: rename variable --- configs/envname/name.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/envname/name.go b/configs/envname/name.go index f4c7e39..61d9a66 100644 --- a/configs/envname/name.go +++ b/configs/envname/name.go @@ -4,5 +4,5 @@ const ( Theme = "THEME" Logger = "LOGGER" Testing = "GO_TESTING" - Storage = "STORAGE" + Space = "SPACE" ) From 5c3a760a88a304bb69320eb48d98bd0e44961251 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:40:06 +0300 Subject: [PATCH 29/88] feat: add todo --- pkg/connect/private_key.go | 1 + pkg/connect/sftp.go | 1 + pkg/connect/ssh.go | 1 + pkg/connect/terminal.go | 1 + 4 files changed, 4 insertions(+) diff --git a/pkg/connect/private_key.go b/pkg/connect/private_key.go index ad98de9..c6e8af7 100644 --- a/pkg/connect/private_key.go +++ b/pkg/connect/private_key.go @@ -8,6 +8,7 @@ import ( "golang.org/x/crypto/ssh" ) +// todo put file in ssh pkg before ssh struct func parsePrivateKey(keyName string, passphrase string) (ssh.Signer, error) { direction, filename := storage.GetDirectionAndFilename(keyName) data, err := storage.Get(direction, filename) diff --git a/pkg/connect/sftp.go b/pkg/connect/sftp.go index e933919..01490d9 100644 --- a/pkg/connect/sftp.go +++ b/pkg/connect/sftp.go @@ -2,6 +2,7 @@ package connect import "github.com/pkg/sftp" +// Sftp todo put in sftp pkg type Sftp struct { Connection *Connect } diff --git a/pkg/connect/ssh.go b/pkg/connect/ssh.go index f184c06..4ef8b67 100644 --- a/pkg/connect/ssh.go +++ b/pkg/connect/ssh.go @@ -10,6 +10,7 @@ import ( "golang.org/x/term" ) +// Ssh todo rename struct to SSH and put in ssh pkg type Ssh struct { Connection *Connect } diff --git a/pkg/connect/terminal.go b/pkg/connect/terminal.go index 5607c2b..3af9a31 100644 --- a/pkg/connect/terminal.go +++ b/pkg/connect/terminal.go @@ -8,6 +8,7 @@ import ( "golang.org/x/term" ) +// todo put file ssh pkg const ( Timeout = 0 From c99ae62c893acac2a8b05128ffd9597e728422a5 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:40:34 +0300 Subject: [PATCH 30/88] fix: delete extra params from method --- internal/storage/storage.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 98662d5..e0dd751 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -9,12 +9,12 @@ import ( ) type Storage interface { - Create(path string, filename string) error - Delete(path string, filename string) error - Exists(path string, filename string) bool - Get(path string, filename string) (string, error) - Write(path string, filename string, data string) error - GetOpenFile(path string, filename string, flags int) (*os.File, error) + Create(filename string) error + Delete(filename string) error + Exists(filename string) bool + Get(filename string) (string, error) + Write(filename string, data string) error + GetOpenFile(filename string, flags int) (*os.File, error) } const CharHidden = "." From 26e96f51c7cca6893a2d780664a0d6648679811c Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:41:03 +0300 Subject: [PATCH 31/88] feat: create file with var for key Space in config --- configs/envconst/space.go | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 configs/envconst/space.go diff --git a/configs/envconst/space.go b/configs/envconst/space.go new file mode 100644 index 0000000..5128b4f --- /dev/null +++ b/configs/envconst/space.go @@ -0,0 +1,8 @@ +package envconst + +const ( + TypeLocalSpace = "local" + TypeSSHConfig = "ssh_config" + TypeGitSpace = "git" + TypeS3Space = "s3" +) From 5bbfe7e94a2602cef4dd073afe651cc6527e4483 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:41:14 +0300 Subject: [PATCH 32/88] feat: add ssh config param in struct --- internal/space/space.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/space/space.go b/internal/space/space.go index 7c5d0df..bcf7e20 100644 --- a/internal/space/space.go +++ b/internal/space/space.go @@ -1,26 +1,29 @@ package space import ( - "fmt" "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/configs/envname" "github.com/misha-ssh/kernel/internal/config" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" + "github.com/misha-ssh/kernel/pkg/ssh" ) type Space struct { - Storage storage.Storage + Storage storage.Storage + SSHConfig *ssh.Config } func New() *Space { - space := &Space{} + space := new(Space) - switch config.Get(envname.Storage) { - case envconst.TypeLocalStorage: + switch config.Get(envname.Space) { + case envconst.TypeLocalSpace: space.Storage = storage.NewLocal() + case envconst.TypeSSHConfig: + space.SSHConfig = ssh.NewConfig() default: - panic(fmt.Sprintf("%s: unknown variable type", envname.Storage)) + panic(envname.Space + ": unknown variable type or undefined") } return space From aeb80770e0c66874979ea6a037bf9fbfafc6a828 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:45:40 +0300 Subject: [PATCH 33/88] fix: delete extra params --- pkg/connect/connect.go | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/pkg/connect/connect.go b/pkg/connect/connect.go index 31bf01f..bdaefb2 100644 --- a/pkg/connect/connect.go +++ b/pkg/connect/connect.go @@ -1,12 +1,5 @@ package connect -type ConnectionType string - -const ( - // TypeSSH type for ssh connection - TypeSSH ConnectionType = "ssh" -) - type Connections struct { Connects []Connect `json:"connects"` } @@ -14,28 +7,23 @@ type Connections struct { // Connect represents a single connection configuration type Connect struct { // Alias is a user-defined name for the connection - Alias string `json:"alias"` - Login string `json:"login"` - Address string `json:"address"` - Password string `json:"password"` + Alias string `json:"alias"` + // Login is the username for authentication + Login string `json:"login"` + // Address is the hostname or IP address of the remote server + Address string `json:"address"` + // Password is the password for authentication + Password string `json:"password"` + + // CreatedAt is the timestamp when this connection was created CreatedAt string `json:"created_at"` + // UpdatedAt is the timestamp when this connection was last modified UpdatedAt string `json:"updated_at"` - // Type specifies the connection protocol (e.g., "ssh") - Type ConnectionType `json:"type"` - - // SshOptions contains SSH-specific configuration options - SshOptions *SshOptions `json:"ssh_options,omitempty"` -} - -// SshOptions contains configuration options specific to SSH connections -type SshOptions struct { - // Port specifies the SSH port (default is 22 if not specified) + // Port specifies the SSH port Port int `json:"port"` - // PrivateKey contains the PEM-encoded private key for authentication PrivateKey string `json:"private_key"` - - // Passphrase pass for private key + // Passphrase is the passphrase for decrypting the private key Passphrase string `json:"passphrase"` } From 0ad94e0d21cb981edb7419a2b373518825660641 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:56:05 +0300 Subject: [PATCH 34/88] fix: put in new pkg --- pkg/{connect => sftp}/sftp.go | 12 ++++++++---- pkg/{connect => ssh}/private_key.go | 2 +- pkg/{connect => ssh}/ssh.go | 5 +++-- pkg/{connect => ssh}/terminal.go | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) rename pkg/{connect => sftp}/sftp.go (59%) rename pkg/{connect => ssh}/private_key.go (98%) rename pkg/{connect => ssh}/ssh.go (96%) rename pkg/{connect => ssh}/terminal.go (98%) diff --git a/pkg/connect/sftp.go b/pkg/sftp/sftp.go similarity index 59% rename from pkg/connect/sftp.go rename to pkg/sftp/sftp.go index 01490d9..63945ca 100644 --- a/pkg/connect/sftp.go +++ b/pkg/sftp/sftp.go @@ -1,14 +1,18 @@ -package connect +package sftp -import "github.com/pkg/sftp" +import ( + "github.com/misha-ssh/kernel/pkg/connect" + ssh2 "github.com/misha-ssh/kernel/pkg/ssh" + "github.com/pkg/sftp" +) // Sftp todo put in sftp pkg type Sftp struct { - Connection *Connect + Connection *connect.Connect } func (s Sftp) Client(opts ...sftp.ClientOption) (*sftp.Client, error) { - ssh := &Ssh{ + ssh := &ssh2.Ssh{ Connection: s.Connection, } diff --git a/pkg/connect/private_key.go b/pkg/ssh/private_key.go similarity index 98% rename from pkg/connect/private_key.go rename to pkg/ssh/private_key.go index c6e8af7..180ab01 100644 --- a/pkg/connect/private_key.go +++ b/pkg/ssh/private_key.go @@ -1,4 +1,4 @@ -package connect +package ssh import ( "strings" diff --git a/pkg/connect/ssh.go b/pkg/ssh/ssh.go similarity index 96% rename from pkg/connect/ssh.go rename to pkg/ssh/ssh.go index 4ef8b67..d0eda6b 100644 --- a/pkg/connect/ssh.go +++ b/pkg/ssh/ssh.go @@ -1,7 +1,8 @@ -package connect +package ssh import ( "fmt" + "github.com/misha-ssh/kernel/pkg/connect" "net" "os" @@ -12,7 +13,7 @@ import ( // Ssh todo rename struct to SSH and put in ssh pkg type Ssh struct { - Connection *Connect + Connection *connect.Connect } // Session establishes a new SSH session with the remote server diff --git a/pkg/connect/terminal.go b/pkg/ssh/terminal.go similarity index 98% rename from pkg/connect/terminal.go rename to pkg/ssh/terminal.go index 3af9a31..c243377 100644 --- a/pkg/connect/terminal.go +++ b/pkg/ssh/terminal.go @@ -1,4 +1,4 @@ -package connect +package ssh import ( "os" From cf4850ef227f35aa776a7602397d777cd5b6bc77 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:56:30 +0300 Subject: [PATCH 35/88] fix: update usage pkg - auto --- examples/ssh/external_port/main.go | 3 ++- examples/ssh/password/main.go | 3 ++- examples/ssh/private_key/main.go | 3 ++- examples/ssh/private_key_passphare/main.go | 3 ++- pkg/kernel/download.go | 3 ++- pkg/kernel/upload.go | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/ssh/external_port/main.go b/examples/ssh/external_port/main.go index 56a3b72..2bd5a0b 100644 --- a/examples/ssh/external_port/main.go +++ b/examples/ssh/external_port/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/misha-ssh/kernel/pkg/ssh" "time" "github.com/misha-ssh/kernel/pkg/connect" @@ -8,7 +9,7 @@ import ( // main for success connect start make command: up-ssh-port func main() { - ssh := &connect.Ssh{ + ssh := &ssh.Ssh{ Connection: &connect.Connect{ Alias: "test", Login: "root", diff --git a/examples/ssh/password/main.go b/examples/ssh/password/main.go index be6fe8e..cae98a7 100644 --- a/examples/ssh/password/main.go +++ b/examples/ssh/password/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/misha-ssh/kernel/pkg/ssh" "time" "github.com/misha-ssh/kernel/pkg/connect" @@ -8,7 +9,7 @@ import ( // main for success connect start make command: up-ssh func main() { - ssh := &connect.Ssh{ + ssh := &ssh.Ssh{ Connection: &connect.Connect{ Alias: "test", Login: "root", diff --git a/examples/ssh/private_key/main.go b/examples/ssh/private_key/main.go index c523ae9..c059cf4 100644 --- a/examples/ssh/private_key/main.go +++ b/examples/ssh/private_key/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/misha-ssh/kernel/pkg/ssh" "time" "github.com/misha-ssh/kernel/pkg/connect" @@ -8,7 +9,7 @@ import ( // main for success connect start make command: up-ssh-key func main() { - ssh := &connect.Ssh{ + ssh := &ssh.Ssh{ Connection: &connect.Connect{ Alias: "test", Login: "root", diff --git a/examples/ssh/private_key_passphare/main.go b/examples/ssh/private_key_passphare/main.go index 8db41f4..7e8f05c 100644 --- a/examples/ssh/private_key_passphare/main.go +++ b/examples/ssh/private_key_passphare/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/misha-ssh/kernel/pkg/ssh" "time" "github.com/misha-ssh/kernel/pkg/connect" @@ -8,7 +9,7 @@ import ( // main for success connect start make command: up-ssh-key-pass func main() { - ssh := &connect.Ssh{ + ssh := &ssh.Ssh{ Connection: &connect.Connect{ Alias: "test", Login: "root", diff --git a/pkg/kernel/download.go b/pkg/kernel/download.go index db553a2..b73868e 100644 --- a/pkg/kernel/download.go +++ b/pkg/kernel/download.go @@ -1,6 +1,7 @@ package kernel import ( + sftp2 "github.com/misha-ssh/kernel/pkg/sftp" "io" "os" @@ -14,7 +15,7 @@ import ( func Download(connection *connect.Connect, downloadRemoteFile string, downloadLocalFile string) error { setup.Init() - sp := connect.Sftp{ + sp := sftp2.Sftp{ Connection: connection, } diff --git a/pkg/kernel/upload.go b/pkg/kernel/upload.go index 8c719b4..ff02f9c 100644 --- a/pkg/kernel/upload.go +++ b/pkg/kernel/upload.go @@ -1,6 +1,7 @@ package kernel import ( + sftp2 "github.com/misha-ssh/kernel/pkg/sftp" "io" "os" @@ -14,7 +15,7 @@ import ( func Upload(connection *connect.Connect, uploadLocalFile string, uploadRemoteFile string) error { setup.Init() - sp := connect.Sftp{ + sp := sftp2.Sftp{ Connection: connection, } From 0057378ad4852561a8f4dbb0d1ae1b6e287793cb Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:56:39 +0300 Subject: [PATCH 36/88] fix: update validate --- pkg/connect/validate.go | 72 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/pkg/connect/validate.go b/pkg/connect/validate.go index 565cbe3..0294884 100644 --- a/pkg/connect/validate.go +++ b/pkg/connect/validate.go @@ -19,14 +19,14 @@ var ( func (c *Connect) Validate() error { for _, err := range []error{ - validateAlias(c.Alias), - validatePrivateKey(c.SshOptions.PrivateKey, c.SshOptions.Passphrase, c.Password), - validatePassword(c.Password, c.SshOptions.PrivateKey), - validateLogin(c.Login), - validateAddress(c.Address), - validateCreatedAt(c.CreatedAt), - validateUpdatedAt(c.UpdatedAt), - validatePort(c.SshOptions.Port), + c.validateAlias(), + c.validatePrivateKey(), + c.validatePassword(), + c.validateLogin(), + c.validateAddress(), + c.validateCreatedAt(), + c.validateUpdatedAt(), + c.validatePort(), } { if err != nil { return err @@ -35,103 +35,103 @@ func (c *Connect) Validate() error { return nil } -func validateAlias(alias string) error { - if strings.TrimSpace(alias) == "" { +func (c *Connect) validateAlias() error { + if strings.TrimSpace(c.Alias) == "" { return errors.New("alias is empty") } - if !aliasPattern.MatchString(alias) { + if !aliasPattern.MatchString(c.Alias) { return errors.New("alias special characters are not allowed") } return nil } -func validateLogin(login string) error { - if strings.TrimSpace(login) == "" { +func (c *Connect) validateLogin() error { + if strings.TrimSpace(c.Login) == "" { return errors.New("login cannot be empty") } - if len(login) > 50 { + if len(c.Login) > 50 { return errors.New("login too long (max 50 characters)") } - if !loginPattern.MatchString(login) { + if !loginPattern.MatchString(c.Login) { return errors.New("login contains invalid characters") } return nil } -func validateAddress(address string) error { - if strings.TrimSpace(address) == "" { +func (c *Connect) validateAddress() error { + if strings.TrimSpace(c.Address) == "" { return errors.New("address cannot be empty") } - if ip := net.ParseIP(address); ip != nil { + if ip := net.ParseIP(c.Address); ip != nil { return nil } - if !addressPattern.MatchString(address) { + if !addressPattern.MatchString(c.Address) { return errors.New("invalid address format") } - if len(address) > 253 { + if len(c.Address) > 253 { return errors.New("address too long") } return nil } -func validatePassword(password string, privateKey string) error { - if strings.TrimSpace(privateKey) != "" { +func (c *Connect) validatePassword() error { + if strings.TrimSpace(c.PrivateKey) != "" { return nil } - if strings.TrimSpace(password) == "" { + if strings.TrimSpace(c.Password) == "" { return errors.New("password cannot be empty") } - if len(password) < 4 { + if len(c.Password) < 4 { return errors.New("password too short (min 4 characters)") } - if len(password) > 100 { + if len(c.Password) > 100 { return errors.New("password too long (max 100 characters)") } return nil } -func validatePrivateKey(privateKey string, passphrase string, password string) error { - if strings.TrimSpace(password) != "" { +func (c *Connect) validatePrivateKey() error { + if strings.TrimSpace(c.Password) != "" { return nil } - block, _ := pem.Decode([]byte(privateKey)) + block, _ := pem.Decode([]byte(c.PrivateKey)) if block == nil { return errors.New("private key is not valid") } - _, err := ssh.ParseRawPrivateKey([]byte(privateKey)) + _, err := ssh.ParseRawPrivateKey([]byte(c.PrivateKey)) if err != nil { if !strings.Contains(err.Error(), "passphrase") { logger.Error(err.Error()) return err } - _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) + _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(c.PrivateKey), []byte(c.Passphrase)) } return err } -func validateCreatedAt(date string) error { - return validateDate(date) +func (c *Connect) validateCreatedAt() error { + return validateDate(c.CreatedAt) } -func validateUpdatedAt(date string) error { - return validateDate(date) +func (c *Connect) validateUpdatedAt() error { + return validateDate(c.UpdatedAt) } func validateDate(date string) error { @@ -151,8 +151,8 @@ func validateDate(date string) error { return nil } -func validatePort(port int) error { - if port < 1 || port > 65535 { +func (c *Connect) validatePort() error { + if c.Port < 1 || c.Port > 65535 { return errors.New("port must be between 1 and 65535") } From 2a36a14b52a48ecf58c0412680df90e372c07de3 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:57:13 +0300 Subject: [PATCH 37/88] fix: update pkg usage --- e2e/kernel/connect_test.go | 7 ++++--- pkg/kernel/connect.go | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/e2e/kernel/connect_test.go b/e2e/kernel/connect_test.go index 623a5df..da33701 100644 --- a/e2e/kernel/connect_test.go +++ b/e2e/kernel/connect_test.go @@ -4,6 +4,7 @@ package kernel import ( "context" + ssh2 "github.com/misha-ssh/kernel/pkg/ssh" "os/exec" "testing" "time" @@ -59,7 +60,7 @@ func TestIntegrationDefaultConnect(t *testing.T) { }, } - sshConnector := &connect.Ssh{ + sshConnector := &ssh2.Ssh{ Connection: connection, } @@ -127,7 +128,7 @@ func TestIntegrationPrivateKeyConnect(t *testing.T) { }, } - sshConnector := &connect.Ssh{ + sshConnector := &ssh2.Ssh{ Connection: connection, } session, err := sshConnector.Session() @@ -193,7 +194,7 @@ func TestIntegrationPrivateKeyConnectWithPassphrase(t *testing.T) { }, } - sshConnector := &connect.Ssh{ + sshConnector := &ssh2.Ssh{ Connection: connection, } session, err := sshConnector.Session() diff --git a/pkg/kernel/connect.go b/pkg/kernel/connect.go index 8ed06bd..284b804 100644 --- a/pkg/kernel/connect.go +++ b/pkg/kernel/connect.go @@ -2,6 +2,7 @@ package kernel import ( "errors" + "github.com/misha-ssh/kernel/pkg/ssh" "github.com/misha-ssh/kernel/internal/logger" "github.com/misha-ssh/kernel/internal/setup" @@ -20,7 +21,7 @@ func Connect(connection *connect.Connect) error { switch connection.Type { case connect.TypeSSH: - ssh := &connect.Ssh{ + ssh := &ssh.Ssh{ Connection: connection, } From 3916ee9ecc4bf74b243458eb8b6f1a926d6a9a75 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 16:57:43 +0300 Subject: [PATCH 38/88] fix: delete extra todo --- pkg/sftp/sftp.go | 1 - pkg/ssh/private_key.go | 1 - pkg/ssh/ssh.go | 1 - pkg/ssh/terminal.go | 1 - 4 files changed, 4 deletions(-) diff --git a/pkg/sftp/sftp.go b/pkg/sftp/sftp.go index 63945ca..3ff7072 100644 --- a/pkg/sftp/sftp.go +++ b/pkg/sftp/sftp.go @@ -6,7 +6,6 @@ import ( "github.com/pkg/sftp" ) -// Sftp todo put in sftp pkg type Sftp struct { Connection *connect.Connect } diff --git a/pkg/ssh/private_key.go b/pkg/ssh/private_key.go index 180ab01..7b00f9d 100644 --- a/pkg/ssh/private_key.go +++ b/pkg/ssh/private_key.go @@ -8,7 +8,6 @@ import ( "golang.org/x/crypto/ssh" ) -// todo put file in ssh pkg before ssh struct func parsePrivateKey(keyName string, passphrase string) (ssh.Signer, error) { direction, filename := storage.GetDirectionAndFilename(keyName) data, err := storage.Get(direction, filename) diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index d0eda6b..95756f9 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -11,7 +11,6 @@ import ( "golang.org/x/term" ) -// Ssh todo rename struct to SSH and put in ssh pkg type Ssh struct { Connection *connect.Connect } diff --git a/pkg/ssh/terminal.go b/pkg/ssh/terminal.go index c243377..91bdcd9 100644 --- a/pkg/ssh/terminal.go +++ b/pkg/ssh/terminal.go @@ -8,7 +8,6 @@ import ( "golang.org/x/term" ) -// todo put file ssh pkg const ( Timeout = 0 From ffa82986ea636755b26f4a6e123a75bf48c0f222 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 17:02:31 +0300 Subject: [PATCH 39/88] feat: add method for create sctruct --- pkg/ssh/ssh.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index 95756f9..e992b22 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -11,12 +11,18 @@ import ( "golang.org/x/term" ) -type Ssh struct { +type SSH struct { Connection *connect.Connect } +func NewSSH(connection *connect.Connect) *SSH { + return &SSH{ + Connection: connection, + } +} + // Session establishes a new SSH session with the remote server -func (s *Ssh) Session() (*ssh.Session, error) { +func (s *SSH) Session() (*ssh.Session, error) { client, err := s.Client() if err != nil { return nil, err @@ -52,7 +58,7 @@ func (s *Ssh) Session() (*ssh.Session, error) { } // Connect starts an interactive shell session using the established SSH connection -func (s *Ssh) Connect(session *ssh.Session) error { +func (s *SSH) Connect(session *ssh.Session) error { defer func() { if err := session.Close(); err != nil { logger.Error(err.Error()) @@ -88,7 +94,7 @@ func (s *Ssh) Connect(session *ssh.Session) error { } // Client create ssh client from config and Auth -func (s *Ssh) Client() (*ssh.Client, error) { +func (s *SSH) Client() (*ssh.Client, error) { sshAuth, err := s.Auth() if err != nil { return nil, err @@ -110,7 +116,7 @@ func (s *Ssh) Client() (*ssh.Client, error) { } // Auth automate defines method auth from Connect -func (s *Ssh) Auth() ([]ssh.AuthMethod, error) { +func (s *SSH) Auth() ([]ssh.AuthMethod, error) { var authMethod []ssh.AuthMethod if len(s.Connection.Password) > 0 { From 8f34c484dbd59e91b4a2d2501490ed61ef38b111 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 15 Nov 2025 17:05:12 +0300 Subject: [PATCH 40/88] feat: add base method --- pkg/ssh/config.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 9c4aa49..ac1cffe 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -2,6 +2,7 @@ package ssh import ( "github.com/misha-ssh/kernel/internal/storage" + "github.com/misha-ssh/kernel/pkg/connect" ) type Config struct { @@ -13,3 +14,19 @@ func NewConfig() *Config { Path: storage.GetDirSSH(), } } + +func (c *Config) GetConnections() (*connect.Connections, error) { + return nil, nil +} + +func (c *Config) SaveConnection(connection *connect.Connect) error { + return nil +} + +func (c *Config) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { + return nil, nil +} + +func (c *Config) DeleteConnection(connection *connect.Connect) error { + return nil +} From 68a534f579bd976a79bc961627d927e466a7e301 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 17 Nov 2025 21:24:02 +0300 Subject: [PATCH 41/88] feat: add bse logic --- pkg/ssh/config.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index ac1cffe..8be6eb8 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -1,21 +1,32 @@ package ssh import ( + "fmt" + "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" ) type Config struct { - Path string + LocalStorage *storage.Local } func NewConfig() *Config { return &Config{ - Path: storage.GetDirSSH(), + LocalStorage: &storage.Local{ + Path: storage.GetDirSSH(), + }, } } func (c *Config) GetConnections() (*connect.Connections, error) { + data, err := c.LocalStorage.Get(envconst.FilenameConfig) + if err != nil { + return nil, err + } + + fmt.Println(data) + return nil, nil } From b73476876b9930d4b1c02c7d690294a746c09b8e Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 17 Nov 2025 21:24:07 +0300 Subject: [PATCH 42/88] feat: add base test --- pkg/ssh/config_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 pkg/ssh/config_test.go diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go new file mode 100644 index 0000000..6324d50 --- /dev/null +++ b/pkg/ssh/config_test.go @@ -0,0 +1,31 @@ +package ssh + +import ( + "testing" + + "github.com/misha-ssh/kernel/pkg/connect" + "github.com/stretchr/testify/assert" +) + +func TestConfig_GetConnections(t *testing.T) { + tests := []struct { + name string + want *connect.Connections + wantErr bool + }{ + { + name: "success - get connections", + want: &connect.Connections{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := NewConfig() + + got, err := config.GetConnections() + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.want, got) + }) + } +} From e61c31ab49dc4437307bf7630f30b71b914ed301 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 17 Nov 2025 21:24:18 +0300 Subject: [PATCH 43/88] fix: change visible path --- internal/storage/local.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/storage/local.go b/internal/storage/local.go index 32441a7..efb73a1 100644 --- a/internal/storage/local.go +++ b/internal/storage/local.go @@ -8,7 +8,7 @@ import ( ) type Local struct { - path string + Path string } var ErrEmptyDirectory = errors.New("empty directory") @@ -16,19 +16,19 @@ var ErrDeleteDirectory = errors.New("get dir, delete only file") func NewLocal() *Local { return &Local{ - path: GetAppDir(), + Path: GetAppDir(), } } // Create creates a new file at the specified path, including parent directories if needed. // Returns error if file creation fails. func (l *Local) Create(filename string) error { - if strings.TrimSpace(l.path) == "" { + if strings.TrimSpace(l.Path) == "" { return ErrEmptyDirectory } - if _, err := os.Stat(l.path); os.IsNotExist(err) { - err = os.Mkdir(l.path, os.ModePerm) + if _, err := os.Stat(l.Path); os.IsNotExist(err) { + err = os.Mkdir(l.Path, os.ModePerm) if err != nil { return err } @@ -43,7 +43,7 @@ func (l *Local) Create(filename string) error { // Delete removes the specified file. Returns error if deletion fails. func (l *Local) Delete(filename string) error { - file := filepath.Join(l.path, filename) + file := filepath.Join(l.Path, filename) info, err := os.Stat(file) if err != nil { return err @@ -53,13 +53,13 @@ func (l *Local) Delete(filename string) error { return ErrDeleteDirectory } - return os.Remove(filepath.Join(l.path, filename)) + return os.Remove(filepath.Join(l.Path, filename)) } // Exists checks if a file exists at the given path and is not a directory. // Returns boolean indicating existence. func (l *Local) Exists(filename string) bool { - info, err := os.Stat(filepath.Join(l.path, filename)) + info, err := os.Stat(filepath.Join(l.Path, filename)) if errors.Is(err, os.ErrNotExist) { return false } @@ -70,7 +70,7 @@ func (l *Local) Exists(filename string) bool { // Get reads and returns the contents of a file as a string. // Returns error if file cannot be read. func (l *Local) Get(filename string) (string, error) { - data, err := os.ReadFile(filepath.Join(l.path, filename)) + data, err := os.ReadFile(filepath.Join(l.Path, filename)) if err != nil { return "", err } @@ -81,7 +81,7 @@ func (l *Local) Get(filename string) (string, error) { // Write saves data to a file, overwriting existing content. // Creates file if it doesn't exist. Returns error on failure. func (l *Local) Write(filename string, data string) error { - err := os.WriteFile(filepath.Join(l.path, filename), []byte(data), os.ModePerm) + err := os.WriteFile(filepath.Join(l.Path, filename), []byte(data), os.ModePerm) if err != nil { return err } @@ -92,7 +92,7 @@ func (l *Local) Write(filename string, data string) error { // GetOpenFile opens a file with specified flags (os.O_RDWR, etc.) and returns the file handle. // Returns error if file cannot be opened. func (l *Local) GetOpenFile(filename string, flags int) (*os.File, error) { - file := filepath.Join(l.path, filename) + file := filepath.Join(l.Path, filename) openFile, err := os.OpenFile(file, flags, os.ModePerm) if err != nil { From 167fcc37b251e7c7cf151f885a062145a146e3e0 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 17 Nov 2025 21:24:41 +0300 Subject: [PATCH 44/88] fix: change logic storage on local storage --- internal/logger/storage.go | 40 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/internal/logger/storage.go b/internal/logger/storage.go index 08fedb4..72ec47b 100644 --- a/internal/logger/storage.go +++ b/internal/logger/storage.go @@ -11,25 +11,27 @@ import ( "github.com/misha-ssh/kernel/internal/storage" ) -const FileName = envconst.FilenameLogger +const Filename = envconst.FilenameLogger var ( - DirectionApp = storage.GetAppDir() - ErrGetStorageInfo = errors.New("err get info use log - storage") ErrCreateStorage = errors.New("err at created log file") ErrGetOpenFile = errors.New("err get open log file") ) -type StorageLogger struct{} +type StorageLogger struct { + storage *storage.Local +} func NewStorageLogger() *StorageLogger { - return &StorageLogger{} + return &StorageLogger{ + storage: storage.NewLocal(), + } } -func (sl *StorageLogger) createLogFile() error { - if !storage.Exists(DirectionApp, FileName) { - err := storage.Create(DirectionApp, FileName) +func (s *StorageLogger) createLogFile() error { + if !s.storage.Exists(Filename) { + err := s.storage.Create(Filename) if err != nil { return err } @@ -38,8 +40,8 @@ func (sl *StorageLogger) createLogFile() error { return nil } -func (sl *StorageLogger) log(value any, status StatusLog) error { - err := sl.createLogFile() +func (s *StorageLogger) log(value any, status StatusLog) error { + err := s.createLogFile() if err != nil { return ErrCreateStorage } @@ -51,7 +53,7 @@ func (sl *StorageLogger) log(value any, status StatusLog) error { logInfo := fmt.Sprintf("|%v| file: %s, line: %v, message: %#v", status, calledFile, line, value) - openLogFile, err := storage.GetOpenFile(DirectionApp, FileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE) + openLogFile, err := s.storage.GetOpenFile(Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE) defer func(openLogFile *os.File) { err = openLogFile.Close() }(openLogFile) @@ -65,29 +67,29 @@ func (sl *StorageLogger) log(value any, status StatusLog) error { return nil } -func (sl *StorageLogger) Error(value any) { - err := sl.log(value, ErrorStatus) +func (s *StorageLogger) Error(value any) { + err := s.log(value, ErrorStatus) if err != nil { panic(err) } } -func (sl *StorageLogger) Debug(value any) { - err := sl.log(value, DebugStatus) +func (s *StorageLogger) Debug(value any) { + err := s.log(value, DebugStatus) if err != nil { panic(err) } } -func (sl *StorageLogger) Info(value any) { - err := sl.log(value, InfoStatus) +func (s *StorageLogger) Info(value any) { + err := s.log(value, InfoStatus) if err != nil { panic(err) } } -func (sl *StorageLogger) Warn(value any) { - err := sl.log(value, WarnStatus) +func (s *StorageLogger) Warn(value any) { + err := s.log(value, WarnStatus) if err != nil { panic(err) } From a840d05d1a1c3bc9e654459b2be8cdcc344fbc98 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:57:39 +0300 Subject: [PATCH 45/88] feat: add method set and get default struct for pkg --- internal/space/space.go | 45 ++++++++++++------------------------- internal/storage/storage.go | 14 ++++++++++++ 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/internal/space/space.go b/internal/space/space.go index bcf7e20..d1d15e8 100644 --- a/internal/space/space.go +++ b/internal/space/space.go @@ -1,46 +1,29 @@ package space import ( - "github.com/misha-ssh/kernel/configs/envconst" - "github.com/misha-ssh/kernel/configs/envname" - "github.com/misha-ssh/kernel/internal/config" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" - "github.com/misha-ssh/kernel/pkg/ssh" ) -type Space struct { - Storage storage.Storage - SSHConfig *ssh.Config +type Space interface { + GetConnections() (*connect.Connections, error) + SaveConnection(connection *connect.Connect) error + UpdateConnection(connection *connect.Connect) (*connect.Connect, error) + DeleteConnection(connection *connect.Connect) error } -func New() *Space { - space := new(Space) +var defaultSpace Space - switch config.Get(envname.Space) { - case envconst.TypeLocalSpace: - space.Storage = storage.NewLocal() - case envconst.TypeSSHConfig: - space.SSHConfig = ssh.NewConfig() - default: - panic(envname.Space + ": unknown variable type or undefined") +func Get() Space { + if defaultSpace == nil { + defaultSpace = &Storage{ + Storage: storage.Get(), + } } - return space + return defaultSpace } -func (s *Space) GetConnections() (*connect.Connections, error) { - return nil, nil -} - -func (s *Space) SaveConnection(connection *connect.Connect) error { - return nil -} - -func (s *Space) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { - return nil, nil -} - -func (s *Space) DeleteConnection(connection *connect.Connect) error { - return nil +func Set(space Space) { + defaultSpace = space } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index e0dd751..94075ce 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -17,6 +17,20 @@ type Storage interface { GetOpenFile(filename string, flags int) (*os.File, error) } +var defaultStorage Storage + +func Get() Storage { + if defaultStorage == nil { + defaultStorage = NewLocal() + } + + return defaultStorage +} + +func Set(storage Storage) { + defaultStorage = storage +} + const CharHidden = "." // GetAppDir get dir application From 302e6df0d448570d00e45f4da4c91b9bc82a49b7 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:58:03 +0300 Subject: [PATCH 46/88] feat: create struct for space interface --- internal/space/ssh_config.go | 26 ++++++++++++++++++++++++++ internal/space/storage.go | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 internal/space/ssh_config.go create mode 100644 internal/space/storage.go diff --git a/internal/space/ssh_config.go b/internal/space/ssh_config.go new file mode 100644 index 0000000..78e5fa0 --- /dev/null +++ b/internal/space/ssh_config.go @@ -0,0 +1,26 @@ +package space + +import ( + "github.com/misha-ssh/kernel/pkg/connect" + "github.com/misha-ssh/kernel/pkg/ssh" +) + +type SSHConfig struct { + Config *ssh.Config +} + +func (s *SSHConfig) GetConnections() (*connect.Connections, error) { + return nil, nil +} + +func (s *SSHConfig) SaveConnection(connection *connect.Connect) error { + return nil +} + +func (s *SSHConfig) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { + return nil, nil +} + +func (s *SSHConfig) DeleteConnection(connection *connect.Connect) error { + return nil +} diff --git a/internal/space/storage.go b/internal/space/storage.go new file mode 100644 index 0000000..3f71a21 --- /dev/null +++ b/internal/space/storage.go @@ -0,0 +1,26 @@ +package space + +import ( + "github.com/misha-ssh/kernel/internal/storage" + "github.com/misha-ssh/kernel/pkg/connect" +) + +type Storage struct { + Storage storage.Storage +} + +func (s *Storage) GetConnections() (*connect.Connections, error) { + return nil, nil +} + +func (s *Storage) SaveConnection(connection *connect.Connect) error { + return nil +} + +func (s *Storage) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { + return nil, nil +} + +func (s *Storage) DeleteConnection(connection *connect.Connect) error { + return nil +} From 343f55bc41060bbd99d67ba584525d24be2bde44 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:58:19 +0300 Subject: [PATCH 47/88] feat: add const type for storage --- configs/envconst/storage.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 configs/envconst/storage.go diff --git a/configs/envconst/storage.go b/configs/envconst/storage.go new file mode 100644 index 0000000..aa0960d --- /dev/null +++ b/configs/envconst/storage.go @@ -0,0 +1,7 @@ +package envconst + +const ( + TypeLocalStorage = "local" + TypeGitStorage = "git" + TypeS3Storage = "s3" +) From 8c6e63303ff85d8f26518c089e90d28d75e5ae6c Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:58:43 +0300 Subject: [PATCH 48/88] fix: update logic used connection arg --- pkg/ssh/ssh.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index e992b22..93d08f9 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -109,7 +109,7 @@ func (s *SSH) Client() (*ssh.Client, error) { hostWithPort := net.JoinHostPort( s.Connection.Address, - fmt.Sprint(s.Connection.SshOptions.Port), + fmt.Sprint(s.Connection.Port), ) return ssh.Dial("tcp", hostWithPort, config) @@ -123,11 +123,8 @@ func (s *SSH) Auth() ([]ssh.AuthMethod, error) { authMethod = append(authMethod, ssh.Password(s.Connection.Password)) } - if len(s.Connection.SshOptions.PrivateKey) > 0 { - key, err := parsePrivateKey( - s.Connection.SshOptions.PrivateKey, - s.Connection.SshOptions.Passphrase, - ) + if len(s.Connection.PrivateKey) > 0 { + key, err := s.parsePrivateKey() if err != nil { return nil, err } From 54922ffbe1501a1382ec9cbc479fc0af10d098ef Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:58:54 +0300 Subject: [PATCH 49/88] fix: delete extra const --- configs/envconst/space.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/configs/envconst/space.go b/configs/envconst/space.go index 5128b4f..3c6bfab 100644 --- a/configs/envconst/space.go +++ b/configs/envconst/space.go @@ -1,8 +1,6 @@ package envconst const ( - TypeLocalSpace = "local" - TypeSSHConfig = "ssh_config" - TypeGitSpace = "git" - TypeS3Space = "s3" + TypeStorageSpace = "storage" + TypeConfigSpace = "config" ) From 95ab404cbdfd5eb35dd33fa184e0248bc6ea6bc0 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:59:37 +0300 Subject: [PATCH 50/88] fix: update logic read private key --- pkg/ssh/private_key.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/ssh/private_key.go b/pkg/ssh/private_key.go index 7b00f9d..42e2a7a 100644 --- a/pkg/ssh/private_key.go +++ b/pkg/ssh/private_key.go @@ -8,9 +8,10 @@ import ( "golang.org/x/crypto/ssh" ) -func parsePrivateKey(keyName string, passphrase string) (ssh.Signer, error) { - direction, filename := storage.GetDirectionAndFilename(keyName) - data, err := storage.Get(direction, filename) +func (s *SSH) parsePrivateKey() (ssh.Signer, error) { + currentStorage := storage.Get() + + data, err := currentStorage.Get(s.Connection.PrivateKey) if err != nil { logger.Error(err.Error()) return nil, err @@ -25,7 +26,7 @@ func parsePrivateKey(keyName string, passphrase string) (ssh.Signer, error) { return nil, err } - key, err = ssh.ParsePrivateKeyWithPassphrase(dataSshKey, []byte(passphrase)) + key, err = ssh.ParsePrivateKeyWithPassphrase(dataSshKey, []byte(s.Connection.Passphrase)) if err != nil { logger.Error(err.Error()) return nil, err From 0f408ce322f7edccaada3ac8b6a1e184c2c2e417 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 21:59:48 +0300 Subject: [PATCH 51/88] feat: add const name - storage --- configs/envname/name.go | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/envname/name.go b/configs/envname/name.go index 61d9a66..9fed746 100644 --- a/configs/envname/name.go +++ b/configs/envname/name.go @@ -5,4 +5,5 @@ const ( Logger = "LOGGER" Testing = "GO_TESTING" Space = "SPACE" + Storage = "STORAGE" ) From f27ee668e317f0d24b7ee1f9ac8d0de41fe4e42b Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 22:00:02 +0300 Subject: [PATCH 52/88] fix: update test for view error --- pkg/ssh/config_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 6324d50..855b153 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -24,7 +24,10 @@ func TestConfig_GetConnections(t *testing.T) { config := NewConfig() got, err := config.GetConnections() - assert.Equal(t, tt.wantErr, err != nil) + if !tt.wantErr { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) }) } From 5d85db954bb6551dd30264a0aa2bea1cc9a978fc Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 18 Nov 2025 22:01:06 +0300 Subject: [PATCH 53/88] feat: add new init logic --- internal/setup/setup.go | 59 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/internal/setup/setup.go b/internal/setup/setup.go index 408b288..9c62a73 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -3,6 +3,8 @@ package setup import ( "encoding/json" "errors" + "github.com/misha-ssh/kernel/internal/space" + "github.com/misha-ssh/kernel/pkg/ssh" "os/user" "github.com/misha-ssh/kernel/configs/envconst" @@ -17,7 +19,9 @@ import ( var ( ErrCreateFileConnection = errors.New("err create file connection") - ErrSetLoggerFromConfig = errors.New("err set logger from configs") + ErrSetLoggerFromConfig = errors.New("err set logger from config") + ErrSetStorageFromConfig = errors.New("err set storage from config") + ErrSetSpaceFromConfig = errors.New("err set space from config") ErrSetDefaultValue = errors.New("err set default value") ErrMarshalJson = errors.New("failed to marshal json") ErrWriteJson = errors.New("failed to write json") @@ -92,9 +96,10 @@ func initFileConfig() error { } defaultValues := map[string]string{ - envname.Theme: envconst.Theme, - envname.Logger: envconst.TypeStorageLogger, - envname.Space: envconst.TypeLocalSpace, + envname.Theme: envconst.Theme, + envname.Logger: envconst.TypeStorageLogger, + envname.Space: envconst.TypeStorageSpace, + envname.Storage: envconst.TypeLocalStorage, } for key, value := range defaultValues { @@ -164,6 +169,42 @@ func initLoggerFromConfig() error { return nil } +func initStorageFromConfig() error { + storageType := config.Get(envname.Storage) + + switch storageType { + case envconst.TypeLocalStorage: + storage.Set(storage.NewLocal()) + default: + return ErrSetStorageFromConfig + } + + return nil +} + +func initSpaceFromConfig() error { + storageType := config.Get(envname.Space) + + switch storageType { + case envconst.TypeStorageSpace: + space.Set( + &space.Storage{ + Storage: storage.Get(), + }, + ) + case envconst.TypeConfigSpace: + space.Set( + &space.SSHConfig{ + Config: ssh.NewConfig(), + }, + ) + default: + return ErrSetSpaceFromConfig + } + + return nil +} + // Init performs complete application initialization: // 1. Config file setup // 2. Logger configuration @@ -188,6 +229,16 @@ func Init() { panic(err) } + err = initStorageFromConfig() + if err != nil { + panic(err) + } + + err = initSpaceFromConfig() + if err != nil { + panic(err) + } + err = initFileConnections() if err != nil { panic(err) From d49a5302c5c2a1b71ffc985b7dc570017082c7d0 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Nov 2025 01:58:04 +0300 Subject: [PATCH 54/88] feat: add base logic for set alias --- pkg/ssh/config.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 8be6eb8..a34afb3 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -1,10 +1,13 @@ package ssh import ( + "bufio" "fmt" "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" + "os" + "strings" ) type Config struct { @@ -20,12 +23,41 @@ func NewConfig() *Config { } func (c *Config) GetConnections() (*connect.Connections, error) { - data, err := c.LocalStorage.Get(envconst.FilenameConfig) + var connections connect.Connections + + file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) if err != nil { return nil, err } - fmt.Println(data) + s := bufio.NewScanner(file) + + for s.Scan() { + line := strings.TrimSpace(s.Text()) + + if strings.HasPrefix(line, "#") { + continue + } + + if strings.Contains(line, "Host") { + value := strings.Split(line, " ") + + if value[0] == "Host" { + connection := new(connect.Connect) + connection.Alias = line[5:] + + connections.Connects = append(connections.Connects, *connection) + } + } + + fmt.Println(line) + } + + fmt.Println(connections) + + if err = s.Err(); err != nil { + fmt.Println("err: ", err) + } return nil, nil } From 642ca83374266caf7be2d8864810a58d8bf73c0c Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Nov 2025 02:00:26 +0300 Subject: [PATCH 55/88] feat: add todo --- pkg/ssh/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index a34afb3..f99239a 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -39,6 +39,7 @@ func (c *Config) GetConnections() (*connect.Connections, error) { continue } + //todo it is necessary to make a strict comparison to distinguish between Host and Hostname if strings.Contains(line, "Host") { value := strings.Split(line, " ") From 7c6a8a91d642ac5ae4abcf77ef7de4dcc5775f11 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Nov 2025 00:31:13 +0300 Subject: [PATCH 56/88] feat: add base logic add data from config --- pkg/ssh/config.go | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index f99239a..9885765 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -3,11 +3,14 @@ package ssh import ( "bufio" "fmt" + "os" + "reflect" + "strconv" + "strings" + "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" - "os" - "strings" ) type Config struct { @@ -32,32 +35,63 @@ func (c *Config) GetConnections() (*connect.Connections, error) { s := bufio.NewScanner(file) + connection := new(connect.Connect) + for s.Scan() { line := strings.TrimSpace(s.Text()) + fmt.Println(line) if strings.HasPrefix(line, "#") { continue } - //todo it is necessary to make a strict comparison to distinguish between Host and Hostname - if strings.Contains(line, "Host") { - value := strings.Split(line, " ") - - if value[0] == "Host" { - connection := new(connect.Connect) - connection.Alias = line[5:] + value := strings.Split(line, " ") + if line == "" { + if !reflect.DeepEqual(connection, &connect.Connect{}) { connections.Connects = append(connections.Connects, *connection) } + + connection = new(connect.Connect) } - fmt.Println(line) + if value[0] == "Host" { + if len(value) < 1 { + continue + } + + if strings.Contains(value[1], "*") || strings.Contains(value[1], "!") { + continue + } + + connection.Alias = value[1] + } + + if value[0] == "HostName" { + connection.Address = value[1] + } + + if value[0] == "User" { + connection.Login = value[1] + } + + if value[0] == "Port" { + port, err := strconv.Atoi(value[1]) + if err != nil { + return nil, err + } + connection.Port = port + } + + if value[0] == "IdentityFile" { + connection.PrivateKey = value[1] + } } fmt.Println(connections) if err = s.Err(); err != nil { - fmt.Println("err: ", err) + return nil, err } return nil, nil From a48904d80549f901a73d3ab67a3b6e447e177fdd Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 22 Nov 2025 01:09:14 +0300 Subject: [PATCH 57/88] feat: add base logic add data from config --- pkg/ssh/config.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 9885765..4102f80 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -55,7 +55,9 @@ func (c *Config) GetConnections() (*connect.Connections, error) { connection = new(connect.Connect) } - if value[0] == "Host" { + aliasColumn := strings.ToLower(value[0]) + + if aliasColumn == "host" { if len(value) < 1 { continue } @@ -67,15 +69,15 @@ func (c *Config) GetConnections() (*connect.Connections, error) { connection.Alias = value[1] } - if value[0] == "HostName" { + if aliasColumn == "hostname" { connection.Address = value[1] } - if value[0] == "User" { + if aliasColumn == "user" { connection.Login = value[1] } - if value[0] == "Port" { + if aliasColumn == "port" { port, err := strconv.Atoi(value[1]) if err != nil { return nil, err @@ -83,7 +85,7 @@ func (c *Config) GetConnections() (*connect.Connections, error) { connection.Port = port } - if value[0] == "IdentityFile" { + if aliasColumn == "identityfile" { connection.PrivateKey = value[1] } } From 807b428920dbca1a928202da7c03816453b9230f Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 22 Nov 2025 01:36:15 +0300 Subject: [PATCH 58/88] feat: add new method for parse connection --- pkg/ssh/config_parse.go | 133 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 pkg/ssh/config_parse.go diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go new file mode 100644 index 0000000..def6e32 --- /dev/null +++ b/pkg/ssh/config_parse.go @@ -0,0 +1,133 @@ +package ssh + +import ( + "bufio" + "fmt" + "github.com/misha-ssh/kernel/configs/envconst" + "github.com/misha-ssh/kernel/pkg/connect" + "os" + "reflect" + "strconv" + "strings" +) + +func parseHost(connection *connect.Connect, values []string) error { + if strings.ToLower(values[0]) != "host" { + return nil + } + + if len(values) < 2 { + return fmt.Errorf("empty host") + } + + if strings.Contains(values[1], "*") || strings.Contains(values[1], "!") { + return nil + } + + connection.Alias = values[1] + + return nil +} + +func parsePort(connection *connect.Connect, values []string) error { + if strings.ToLower(values[0]) != "port" { + return nil + } + + if len(values) < 2 { + return fmt.Errorf("empty port") + } + + port, err := strconv.Atoi(values[1]) + if err != nil { + return err + } + + connection.Port = port + + return nil +} + +func parseLogin(connection *connect.Connect, values []string) error { + if strings.ToLower(values[0]) != "user" { + return nil + } + + if len(values) < 2 { + return fmt.Errorf("empty user") + } + + connection.Login = values[1] + + return nil +} + +func parsePrivateKey(connection *connect.Connect, values []string) error { + if strings.ToLower(values[0]) != "identityfile" { + return nil + } + + if len(values) < 2 { + return fmt.Errorf("empty private key") + } + + connection.PrivateKey = values[1] + + return nil +} + +func addConnection(connection *connect.Connect, connections *connect.Connections) { + if !reflect.DeepEqual(connection, &connect.Connect{}) { + connections.Connects = append(connections.Connects, *connection) + } + + connection = new(connect.Connect) +} + +func (c *Config) parse() (*connect.Connections, error) { + var connections connect.Connections + + file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) + if err != nil { + return nil, err + } + + s := bufio.NewScanner(file) + + connection := new(connect.Connect) + + for s.Scan() { + line := strings.TrimSpace(s.Text()) + fmt.Println(line) + + if strings.HasPrefix(line, "#") { + continue + } + + if line == "" { + addConnection(connection, &connections) + continue + } + + aliasValues := strings.Split(line, " ") + + for _, err = range []error{ + parseHost(connection, aliasValues), + parsePort(connection, aliasValues), + parseLogin(connection, aliasValues), + parsePrivateKey(connection, aliasValues), + } { + if err != nil { + return nil, err + } + } + } + + fmt.Println(connections) + + if err = s.Err(); err != nil { + return nil, err + } + + return nil, nil +} From c4c0971516a1c8b29d982578058488922ca814b3 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 22 Nov 2025 01:58:44 +0300 Subject: [PATCH 59/88] fix: update view methods --- pkg/ssh/config.go | 72 ++--------------------------------------- pkg/ssh/config_parse.go | 27 ++++++---------- 2 files changed, 11 insertions(+), 88 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 4102f80..e0134cc 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -2,15 +2,10 @@ package ssh import ( "bufio" - "fmt" - "os" - "reflect" - "strconv" - "strings" - "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" + "os" ) type Config struct { @@ -26,75 +21,12 @@ func NewConfig() *Config { } func (c *Config) GetConnections() (*connect.Connections, error) { - var connections connect.Connections - file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) if err != nil { return nil, err } - s := bufio.NewScanner(file) - - connection := new(connect.Connect) - - for s.Scan() { - line := strings.TrimSpace(s.Text()) - fmt.Println(line) - - if strings.HasPrefix(line, "#") { - continue - } - - value := strings.Split(line, " ") - - if line == "" { - if !reflect.DeepEqual(connection, &connect.Connect{}) { - connections.Connects = append(connections.Connects, *connection) - } - - connection = new(connect.Connect) - } - - aliasColumn := strings.ToLower(value[0]) - - if aliasColumn == "host" { - if len(value) < 1 { - continue - } - - if strings.Contains(value[1], "*") || strings.Contains(value[1], "!") { - continue - } - - connection.Alias = value[1] - } - - if aliasColumn == "hostname" { - connection.Address = value[1] - } - - if aliasColumn == "user" { - connection.Login = value[1] - } - - if aliasColumn == "port" { - port, err := strconv.Atoi(value[1]) - if err != nil { - return nil, err - } - connection.Port = port - } - - if aliasColumn == "identityfile" { - connection.PrivateKey = value[1] - } - } - - fmt.Println(connections) - - if err = s.Err(); err != nil { - return nil, err - } + _, _ = parseConnection(bufio.NewScanner(file)) return nil, nil } diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index def6e32..61860b2 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -3,12 +3,11 @@ package ssh import ( "bufio" "fmt" - "github.com/misha-ssh/kernel/configs/envconst" - "github.com/misha-ssh/kernel/pkg/connect" - "os" "reflect" "strconv" "strings" + + "github.com/misha-ssh/kernel/pkg/connect" ) func parseHost(connection *connect.Connect, values []string) error { @@ -77,23 +76,15 @@ func parsePrivateKey(connection *connect.Connect, values []string) error { } func addConnection(connection *connect.Connect, connections *connect.Connections) { - if !reflect.DeepEqual(connection, &connect.Connect{}) { + if !reflect.DeepEqual(connection, new(connect.Connect)) { connections.Connects = append(connections.Connects, *connection) } connection = new(connect.Connect) } -func (c *Config) parse() (*connect.Connections, error) { - var connections connect.Connections - - file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) - if err != nil { - return nil, err - } - - s := bufio.NewScanner(file) - +func parseConnection(s *bufio.Scanner) (*connect.Connections, error) { + connections := new(connect.Connections) connection := new(connect.Connect) for s.Scan() { @@ -105,13 +96,13 @@ func (c *Config) parse() (*connect.Connections, error) { } if line == "" { - addConnection(connection, &connections) + addConnection(connection, connections) continue } aliasValues := strings.Split(line, " ") - for _, err = range []error{ + for _, err := range []error{ parseHost(connection, aliasValues), parsePort(connection, aliasValues), parseLogin(connection, aliasValues), @@ -125,9 +116,9 @@ func (c *Config) parse() (*connect.Connections, error) { fmt.Println(connections) - if err = s.Err(); err != nil { + if err := s.Err(); err != nil { return nil, err } - return nil, nil + return connections, nil } From b73f7e673229f66ba7dca2995fa8a576ae10e980 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 22 Nov 2025 02:06:03 +0300 Subject: [PATCH 60/88] fix: delete extra logic --- pkg/ssh/config_parse.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index 61860b2..e5ca269 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -75,14 +75,6 @@ func parsePrivateKey(connection *connect.Connect, values []string) error { return nil } -func addConnection(connection *connect.Connect, connections *connect.Connections) { - if !reflect.DeepEqual(connection, new(connect.Connect)) { - connections.Connects = append(connections.Connects, *connection) - } - - connection = new(connect.Connect) -} - func parseConnection(s *bufio.Scanner) (*connect.Connections, error) { connections := new(connect.Connections) connection := new(connect.Connect) @@ -96,7 +88,12 @@ func parseConnection(s *bufio.Scanner) (*connect.Connections, error) { } if line == "" { - addConnection(connection, connections) + if !reflect.DeepEqual(connection, new(connect.Connect)) { + connections.Connects = append(connections.Connects, *connection) + } + + connection = new(connect.Connect) + continue } From 220608bd76a268f67c63e266fdedb48103e2ecff Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 22 Nov 2025 02:16:41 +0300 Subject: [PATCH 61/88] feat: add todo --- pkg/ssh/config_parse.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index e5ca269..8382c02 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -10,7 +10,7 @@ import ( "github.com/misha-ssh/kernel/pkg/connect" ) -func parseHost(connection *connect.Connect, values []string) error { +func parseAlias(connection *connect.Connect, values []string) error { if strings.ToLower(values[0]) != "host" { return nil } @@ -19,6 +19,8 @@ func parseHost(connection *connect.Connect, values []string) error { return fmt.Errorf("empty host") } + //todo add logic for pase host with * and ! for set data + //todo this is operation used in last order if strings.Contains(values[1], "*") || strings.Contains(values[1], "!") { return nil } @@ -100,7 +102,7 @@ func parseConnection(s *bufio.Scanner) (*connect.Connections, error) { aliasValues := strings.Split(line, " ") for _, err := range []error{ - parseHost(connection, aliasValues), + parseAlias(connection, aliasValues), parsePort(connection, aliasValues), parseLogin(connection, aliasValues), parsePrivateKey(connection, aliasValues), From bb73fb40226785f508fcd1c4e0b582c132dd762e Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Nov 2025 14:49:22 +0300 Subject: [PATCH 62/88] fix: rename method --- pkg/ssh/config.go | 2 +- pkg/ssh/config_parse.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index e0134cc..47c516c 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -26,7 +26,7 @@ func (c *Config) GetConnections() (*connect.Connections, error) { return nil, err } - _, _ = parseConnection(bufio.NewScanner(file)) + _, _ = parseConnections(bufio.NewScanner(file)) return nil, nil } diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index 8382c02..240214c 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -77,7 +77,7 @@ func parsePrivateKey(connection *connect.Connect, values []string) error { return nil } -func parseConnection(s *bufio.Scanner) (*connect.Connections, error) { +func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { connections := new(connect.Connections) connection := new(connect.Connect) From 56dcf47837886cb2601afdcc66e18c11ec713686 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Nov 2025 20:04:10 +0300 Subject: [PATCH 63/88] fix: update logic parsing data form config --- pkg/ssh/config_parse.go | 98 ++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index 240214c..e183fb0 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -3,13 +3,17 @@ package ssh import ( "bufio" "fmt" - "reflect" "strconv" "strings" "github.com/misha-ssh/kernel/pkg/connect" ) +type ConfigParser struct { + connections *connect.Connections + current *connect.Connect +} + func parseAlias(connection *connect.Connect, values []string) error { if strings.ToLower(values[0]) != "host" { return nil @@ -26,7 +30,6 @@ func parseAlias(connection *connect.Connect, values []string) error { } connection.Alias = values[1] - return nil } @@ -41,11 +44,10 @@ func parsePort(connection *connect.Connect, values []string) error { port, err := strconv.Atoi(values[1]) if err != nil { - return err + return fmt.Errorf("invalid port: %q", values[1]) } connection.Port = port - return nil } @@ -59,7 +61,6 @@ func parseLogin(connection *connect.Connect, values []string) error { } connection.Login = values[1] - return nil } @@ -73,51 +74,86 @@ func parsePrivateKey(connection *connect.Connect, values []string) error { } connection.PrivateKey = values[1] - return nil } -func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { - connections := new(connect.Connections) - connection := new(connect.Connect) +func isComment(line string) bool { + return strings.HasPrefix(strings.TrimSpace(line), "#") +} - for s.Scan() { - line := strings.TrimSpace(s.Text()) - fmt.Println(line) +func isEmptyLine(line string) bool { + return strings.TrimSpace(line) == "" +} - if strings.HasPrefix(line, "#") { - continue - } +func (p *ConfigParser) parseLine(line string) error { + values := strings.Fields(line) + if len(values) == 0 { + return nil + } - if line == "" { - if !reflect.DeepEqual(connection, new(connect.Connect)) { - connections.Connects = append(connections.Connects, *connection) - } + parsers := []func(*connect.Connect, []string) error{ + parseAlias, + parsePort, + parseLogin, + parsePrivateKey, + } - connection = new(connect.Connect) + for _, parser := range parsers { + if err := parser(p.current, values); err != nil { + return err + } + } - continue + return nil +} + +func (p *ConfigParser) saveCurrentConnection() { + if p.current != nil && !p.isConnectionEmpty() { + if p.current.Port == 0 { + p.current.Port = 22 } - aliasValues := strings.Split(line, " ") + p.connections.Connects = append(p.connections.Connects, *p.current) + } + + p.current = &connect.Connect{} +} + +func (p *ConfigParser) isConnectionEmpty() bool { + return p.current.Alias == "" && + p.current.Port == 0 && + p.current.Login == "" && + p.current.PrivateKey == "" +} - for _, err := range []error{ - parseAlias(connection, aliasValues), - parsePort(connection, aliasValues), - parseLogin(connection, aliasValues), - parsePrivateKey(connection, aliasValues), - } { - if err != nil { +func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { + parser := &ConfigParser{ + connections: &connect.Connections{}, + current: &connect.Connect{}, + } + + for s.Scan() { + line := strings.TrimSpace(s.Text()) + fmt.Println(line) + + switch { + case isComment(line): + continue + case isEmptyLine(line): + parser.saveCurrentConnection() + default: + if err := parser.parseLine(line); err != nil { return nil, err } } } - fmt.Println(connections) + parser.saveCurrentConnection() if err := s.Err(); err != nil { return nil, err } - return connections, nil + fmt.Println(parser.connections) + return parser.connections, nil } From 2067243a452c7bb0b365fbc6a56e097b89923e7f Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Nov 2025 20:20:39 +0300 Subject: [PATCH 64/88] fix: delete extra struct with her logic --- pkg/ssh/config_parse.go | 49 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index e183fb0..4e46079 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -9,11 +9,6 @@ import ( "github.com/misha-ssh/kernel/pkg/connect" ) -type ConfigParser struct { - connections *connect.Connections - current *connect.Connect -} - func parseAlias(connection *connect.Connect, values []string) error { if strings.ToLower(values[0]) != "host" { return nil @@ -23,7 +18,7 @@ func parseAlias(connection *connect.Connect, values []string) error { return fmt.Errorf("empty host") } - //todo add logic for pase host with * and ! for set data + //todo add logic for parse host with * and ! for set data //todo this is operation used in last order if strings.Contains(values[1], "*") || strings.Contains(values[1], "!") { return nil @@ -85,7 +80,7 @@ func isEmptyLine(line string) bool { return strings.TrimSpace(line) == "" } -func (p *ConfigParser) parseLine(line string) error { +func parseLine(connection *connect.Connect, line string) error { values := strings.Fields(line) if len(values) == 0 { return nil @@ -99,7 +94,7 @@ func (p *ConfigParser) parseLine(line string) error { } for _, parser := range parsers { - if err := parser(p.current, values); err != nil { + if err := parser(connection, values); err != nil { return err } } @@ -107,30 +102,28 @@ func (p *ConfigParser) parseLine(line string) error { return nil } -func (p *ConfigParser) saveCurrentConnection() { - if p.current != nil && !p.isConnectionEmpty() { - if p.current.Port == 0 { - p.current.Port = 22 +func saveCurrentConnection(connection *connect.Connect, connections *connect.Connections) { + if connection != nil && !isConnectionEmpty(connection) { + if connection.Port == 0 { + connection.Port = 22 } - p.connections.Connects = append(p.connections.Connects, *p.current) + connections.Connects = append(connections.Connects, *connection) } - p.current = &connect.Connect{} + *connection = connect.Connect{} } -func (p *ConfigParser) isConnectionEmpty() bool { - return p.current.Alias == "" && - p.current.Port == 0 && - p.current.Login == "" && - p.current.PrivateKey == "" +func isConnectionEmpty(connection *connect.Connect) bool { + return connection.Alias == "" && + connection.Port == 0 && + connection.Login == "" && + connection.PrivateKey == "" } func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { - parser := &ConfigParser{ - connections: &connect.Connections{}, - current: &connect.Connect{}, - } + connections := new(connect.Connections) + current := new(connect.Connect) for s.Scan() { line := strings.TrimSpace(s.Text()) @@ -140,20 +133,20 @@ func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { case isComment(line): continue case isEmptyLine(line): - parser.saveCurrentConnection() + saveCurrentConnection(current, connections) default: - if err := parser.parseLine(line); err != nil { + if err := parseLine(current, line); err != nil { return nil, err } } } - parser.saveCurrentConnection() + saveCurrentConnection(current, connections) if err := s.Err(); err != nil { return nil, err } - fmt.Println(parser.connections) - return parser.connections, nil + fmt.Println(connections) + return connections, nil } From 974525c6d67a7162879b60fc9f457761991e1e7f Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:54:39 +0300 Subject: [PATCH 65/88] feat: create test util method --- testutil/config.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 testutil/config.go diff --git a/testutil/config.go b/testutil/config.go new file mode 100644 index 0000000..144b9fd --- /dev/null +++ b/testutil/config.go @@ -0,0 +1,20 @@ +package testutil + +import ( + "os" + "path/filepath" +) + +func CreateSSHConfig(tmpDir string, file string) error { + sourceData, err := os.ReadFile(file) + if err != nil { + return err + } + + destPath := filepath.Join(tmpDir, "config") + if err = os.WriteFile(destPath, sourceData, 0644); err != nil { + return err + } + + return nil +} From 07ddff579884463edfb0d788142d0c61f15d0344 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:54:54 +0300 Subject: [PATCH 66/88] fix: update logic parse connection --- pkg/ssh/config_parse.go | 42 ++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index 4e46079..de5ab99 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -3,6 +3,8 @@ package ssh import ( "bufio" "fmt" + "os" + "path/filepath" "strconv" "strings" @@ -18,8 +20,6 @@ func parseAlias(connection *connect.Connect, values []string) error { return fmt.Errorf("empty host") } - //todo add logic for parse host with * and ! for set data - //todo this is operation used in last order if strings.Contains(values[1], "*") || strings.Contains(values[1], "!") { return nil } @@ -28,6 +28,19 @@ func parseAlias(connection *connect.Connect, values []string) error { return nil } +func parseAddress(connection *connect.Connect, values []string) error { + if strings.ToLower(values[0]) != "hostname" { + return nil + } + + if len(values) < 2 { + return fmt.Errorf("empty hostname") + } + + connection.Address = values[1] + return nil +} + func parsePort(connection *connect.Connect, values []string) error { if strings.ToLower(values[0]) != "port" { return nil @@ -68,16 +81,27 @@ func parsePrivateKey(connection *connect.Connect, values []string) error { return fmt.Errorf("empty private key") } - connection.PrivateKey = values[1] + pathKey := values[1] + + if strings.HasPrefix(pathKey, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return err + } + + pathKey = filepath.Join(home, pathKey[2:]) + } + + connection.PrivateKey = pathKey return nil } func isComment(line string) bool { - return strings.HasPrefix(strings.TrimSpace(line), "#") + return strings.HasPrefix(line, "#") } func isEmptyLine(line string) bool { - return strings.TrimSpace(line) == "" + return line == "" } func parseLine(connection *connect.Connect, line string) error { @@ -88,8 +112,9 @@ func parseLine(connection *connect.Connect, line string) error { parsers := []func(*connect.Connect, []string) error{ parseAlias, - parsePort, + parseAddress, parseLogin, + parsePort, parsePrivateKey, } @@ -108,7 +133,9 @@ func saveCurrentConnection(connection *connect.Connect, connections *connect.Con connection.Port = 22 } - connections.Connects = append(connections.Connects, *connection) + if err := connection.Validate(); err == nil { + connections.Connects = append(connections.Connects, *connection) + } } *connection = connect.Connect{} @@ -118,6 +145,7 @@ func isConnectionEmpty(connection *connect.Connect) bool { return connection.Alias == "" && connection.Port == 0 && connection.Login == "" && + connection.Address == "" && connection.PrivateKey == "" } From 4196fdf23e9730bca0aa5f124fdcbf966b720afb Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:55:06 +0300 Subject: [PATCH 67/88] fix: rename make command --- Makefile | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 08dc6f4..6feb945 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,6 @@ test-coverage: # use tests-integration for check integration tests cases # you need an installed docker to work -.PHONY: tests-integration -tests-integration: +.PHONY: test-e2e +test-e2e: GO_TESTING=true go test -tags=integration ./... \ No newline at end of file diff --git a/README.md b/README.md index ba732a7..6982db2 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ make test-coverage Run test e2e: ```bash -make tests-integration +make test-e2e ``` ## 🤝 Feedback From 57c10efa52335655e0c406fca8d88443fa1d9403 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:55:23 +0300 Subject: [PATCH 68/88] fix: change visibility method --- testutil/private_key.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testutil/private_key.go b/testutil/private_key.go index 65b7305..9f08ba7 100644 --- a/testutil/private_key.go +++ b/testutil/private_key.go @@ -9,8 +9,8 @@ import ( "path/filepath" ) -// GeneratePrivateKey generate private key for ssh connect -func GeneratePrivateKey(pass string) ([]byte, error) { +// generatePrivateKey generate private key for ssh connect +func generatePrivateKey(pass string) ([]byte, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, err @@ -37,7 +37,7 @@ func GeneratePrivateKey(pass string) ([]byte, error) { // CreatePrivateKey generate and save private key in file func CreatePrivateKey(direction string) (string, error) { - privatePEM, err := GeneratePrivateKey("") + privatePEM, err := generatePrivateKey("") if err != nil { return "", err } @@ -53,7 +53,7 @@ func CreatePrivateKey(direction string) (string, error) { // CreatePrivateKeyWithPass generate and save private key with pass in file func CreatePrivateKeyWithPass(direction string, pass string) (string, error) { - privatePEM, err := GeneratePrivateKey(pass) + privatePEM, err := generatePrivateKey(pass) if err != nil { return "", err } From 301b4282a23cba9cfee6cca0591e7735aee0443b Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:55:34 +0300 Subject: [PATCH 69/88] fix: update logic validate connection --- pkg/connect/validate.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/connect/validate.go b/pkg/connect/validate.go index 0294884..f6efd03 100644 --- a/pkg/connect/validate.go +++ b/pkg/connect/validate.go @@ -3,9 +3,9 @@ package connect import ( "encoding/pem" "errors" - "github.com/misha-ssh/kernel/internal/logger" "golang.org/x/crypto/ssh" "net" + "os" "regexp" "strings" "time" @@ -108,19 +108,23 @@ func (c *Connect) validatePrivateKey() error { return nil } - block, _ := pem.Decode([]byte(c.PrivateKey)) + data, err := os.ReadFile(c.PrivateKey) + if err != nil { + return errors.New("note found private key") + } + + block, _ := pem.Decode(data) if block == nil { return errors.New("private key is not valid") } - _, err := ssh.ParseRawPrivateKey([]byte(c.PrivateKey)) + _, err = ssh.ParseRawPrivateKey(data) if err != nil { if !strings.Contains(err.Error(), "passphrase") { - logger.Error(err.Error()) return err } - _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(c.PrivateKey), []byte(c.Passphrase)) + _, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(c.Passphrase)) } return err @@ -136,7 +140,7 @@ func (c *Connect) validateUpdatedAt() error { func validateDate(date string) error { if strings.TrimSpace(date) == "" { - return errors.New("date cannot be empty") + return nil } parsedTime, err := time.Parse(time.RFC3339, date) From 7a9adb0995576332dc2bee4ce28daaffced2811d Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 14:55:44 +0300 Subject: [PATCH 70/88] feat: add test data for config --- pkg/ssh/testdata/config | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 pkg/ssh/testdata/config diff --git a/pkg/ssh/testdata/config b/pkg/ssh/testdata/config new file mode 100644 index 0000000..68b4a32 --- /dev/null +++ b/pkg/ssh/testdata/config @@ -0,0 +1,21 @@ +Host targaryen + HostName 192.168.1.10 + User daenerys + Port 7654 + IdentityFile ~/.ssh/id_ed25519 + +Host tyrell + HostName 192.168.10.20 + +Host martell + HostName 192.168.10.50 + +Host *ell + user oberyn + +Host * !martell + LogLevel INFO + +Host * + User root + Compression yes \ No newline at end of file From c43a8ae532fad10694b4032787d5b2678e5a2b3a Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 15:03:32 +0300 Subject: [PATCH 71/88] fix: update return data --- pkg/ssh/config.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 47c516c..102c756 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -26,9 +26,7 @@ func (c *Config) GetConnections() (*connect.Connections, error) { return nil, err } - _, _ = parseConnections(bufio.NewScanner(file)) - - return nil, nil + return parseConnections(bufio.NewScanner(file)) } func (c *Config) SaveConnection(connection *connect.Connect) error { From 63bd17b9645bee8552589a6817d20824cc47d4f6 Mon Sep 17 00:00:00 2001 From: Denis Date: Sat, 29 Nov 2025 15:16:45 +0300 Subject: [PATCH 72/88] feat: add example logic for create config from testdata --- pkg/ssh/config_test.go | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 855b153..11d4321 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -1,34 +1,50 @@ package ssh import ( + "github.com/misha-ssh/kernel/internal/storage" + "github.com/misha-ssh/kernel/testutil" + "github.com/stretchr/testify/require" "testing" "github.com/misha-ssh/kernel/pkg/connect" - "github.com/stretchr/testify/assert" ) func TestConfig_GetConnections(t *testing.T) { tests := []struct { - name string - want *connect.Connections - wantErr bool + name string + want *connect.Connections + filename string + wantErr bool }{ { - name: "success - get connections", - want: &connect.Connections{}, - wantErr: false, + name: "success - get connections", + want: &connect.Connections{}, + filename: "testdata/config", + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config := NewConfig() + tmpDir := t.TempDir() + + err := testutil.CreateSSHConfig(tmpDir, tt.filename) + require.NoError(t, err) + + _, err = testutil.CreatePrivateKey(tmpDir) + require.NoError(t, err) + + config := &Config{ + LocalStorage: &storage.Local{ + Path: tmpDir, + }, + } got, err := config.GetConnections() if !tt.wantErr { - assert.NoError(t, err) + require.NoError(t, err) } - assert.Equal(t, tt.want, got) + require.Equal(t, tt.want, got) }) } } From d18fea8f2855f1c5d40ff976006a0eeec7d269f0 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:39:27 +0300 Subject: [PATCH 73/88] feat: add todo --- testutil/private_key.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testutil/private_key.go b/testutil/private_key.go index 9f08ba7..79ad709 100644 --- a/testutil/private_key.go +++ b/testutil/private_key.go @@ -9,6 +9,8 @@ import ( "path/filepath" ) +// todo delete method and usage file from testdata with pk + // generatePrivateKey generate private key for ssh connect func generatePrivateKey(pass string) ([]byte, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 4096) From cbe4411fa3bc1fce2212c2cbdeefeca18398c863 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:39:33 +0300 Subject: [PATCH 74/88] feat: add test data --- pkg/ssh/testdata/invalid_private_key | 2 ++ pkg/ssh/testdata/private_key | 49 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 pkg/ssh/testdata/invalid_private_key create mode 100644 pkg/ssh/testdata/private_key diff --git a/pkg/ssh/testdata/invalid_private_key b/pkg/ssh/testdata/invalid_private_key new file mode 100644 index 0000000..d2421ec --- /dev/null +++ b/pkg/ssh/testdata/invalid_private_key @@ -0,0 +1,2 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +-----END OPENSSH PRIVATE KEY----- \ No newline at end of file diff --git a/pkg/ssh/testdata/private_key b/pkg/ssh/testdata/private_key new file mode 100644 index 0000000..686aa88 --- /dev/null +++ b/pkg/ssh/testdata/private_key @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- \ No newline at end of file From 18394f18a107b96e6a65109d53fcb39fb7e56ff3 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:39:43 +0300 Subject: [PATCH 75/88] feat: add new test case --- pkg/ssh/config_test.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 11d4321..3023f63 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -17,8 +17,18 @@ func TestConfig_GetConnections(t *testing.T) { wantErr bool }{ { - name: "success - get connections", - want: &connect.Connections{}, + name: "success - get connections", + want: &connect.Connections{ + Connects: []connect.Connect{ + { + Alias: "test", + Address: "localhost", + Login: "user", + Port: 3333, + PrivateKey: "testdata/private_key", + }, + }, + }, filename: "testdata/config", wantErr: false, }, @@ -30,9 +40,6 @@ func TestConfig_GetConnections(t *testing.T) { err := testutil.CreateSSHConfig(tmpDir, tt.filename) require.NoError(t, err) - _, err = testutil.CreatePrivateKey(tmpDir) - require.NoError(t, err) - config := &Config{ LocalStorage: &storage.Local{ Path: tmpDir, From 9b37d49fa042ef7e62794eee2ca9ccfb4afa40a2 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:40:00 +0300 Subject: [PATCH 76/88] fix: update config --- pkg/ssh/testdata/config | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/ssh/testdata/config b/pkg/ssh/testdata/config index 68b4a32..3135b85 100644 --- a/pkg/ssh/testdata/config +++ b/pkg/ssh/testdata/config @@ -1,8 +1,14 @@ -Host targaryen - HostName 192.168.1.10 - User daenerys - Port 7654 - IdentityFile ~/.ssh/id_ed25519 +Host test + HostName localhost + User user + Port 3333 + IdentityFile testdata/private_key + +Host test2 + HostName localhost + User user2 + Port 3333 + IdentityFile testdata/invalid_private_key Host tyrell HostName 192.168.10.20 From 317ad525d12da03bd57f68b3c5b5fd2e0ebd6a1d Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:45:22 +0300 Subject: [PATCH 77/88] feat: create empty config --- pkg/ssh/testdata/empty_config | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pkg/ssh/testdata/empty_config diff --git a/pkg/ssh/testdata/empty_config b/pkg/ssh/testdata/empty_config new file mode 100644 index 0000000..e69de29 From c6cdd20301dc2a51bc579cb5e70010283162a5a9 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:45:35 +0300 Subject: [PATCH 78/88] feat: add new test cases --- pkg/ssh/config_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 3023f63..d722338 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -32,13 +32,29 @@ func TestConfig_GetConnections(t *testing.T) { filename: "testdata/config", wantErr: false, }, + { + name: "success - get empty connections", + want: &connect.Connections{ + Connects: nil, + }, + filename: "testdata/empty_config", + wantErr: false, + }, + { + name: "err - note exists config", + want: nil, + filename: "", + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tmpDir := t.TempDir() - err := testutil.CreateSSHConfig(tmpDir, tt.filename) - require.NoError(t, err) + if tt.filename != "" { + err := testutil.CreateSSHConfig(tmpDir, tt.filename) + require.NoError(t, err) + } config := &Config{ LocalStorage: &storage.Local{ From 1d3a967acb3d9fae04251678d02f5bd866239b45 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:53:48 +0300 Subject: [PATCH 79/88] fix: update key --- pkg/ssh/testdata/private_key | 100 ++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/pkg/ssh/testdata/private_key b/pkg/ssh/testdata/private_key index 686aa88..af08f13 100644 --- a/pkg/ssh/testdata/private_key +++ b/pkg/ssh/testdata/private_key @@ -1,49 +1,51 @@ ------BEGIN OPENSSH PRIVATE KEY----- -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 ------END OPENSSH PRIVATE KEY----- \ No newline at end of file +-----BEGIN RSA PRIVATE KEY----- +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 +Yo0RAoIBAQCj8+TuZ6cUpfJHX4e2Ik5ZeMPTPxZgOe29AmrzCEtN4a0B56mgaCfU +5x0T37RtGJWjkGZvxDvb0QNAPTTd68b66uoXU+SIhEwMxmoW/Kto35Sw7MvfPxI3 +5lfnQSKmwU3cqHS0Wiqtl8c3gub4cq3a1vdf/4d4czgiUGr5MYr4fTvzPZ7LKimS +0k/MMj6BG6Z/sz5mPKw9DPzJAyHaiL7XiwuoTUNNZ6FXHD+YdTg2Ns75HW+n86Th +rISl34yerbppGRKg2fGKuPGRe3sPzlXO/TL532AGlXbj2GpO6HK4LOTEIYJLrzwa +t/udeZ6OcM2l50VhS4BbZy6b2gVogJlBAoIBAD3ncarSkt6SW5Jj/XAyTdXuc4dl +s58KtaxIMeaes4KnArEPb3LpqIq03rXCFeCQb29FjbqB6fLXNheyXhWhT21+Je79 +Q4PgAA0QmW6M5XC3kE8gmuwHaN/RO3dhNqHn4Sri5+6rp7BYd8jDm8kNP/KVpCXH +D+BFJ8WcN++2lDN91Sp/1PDujsb9lm5ybTBPZRVOl9bkEMfawg86BudUXysNMAvE +o6xnLn+dfopf/Dzn1XdU9/tkjN2JlJIVDJmoH56kxhpsOY1qcJw0ghmad9Mk6Hd6 +WnP2xHffdHxv2SI/XgeqG/cQRyl/Hlg1/SeLspHN060BeyNAiercfSIVDSQ= +-----END RSA PRIVATE KEY----- \ No newline at end of file From 635d99c0b7c5108be89a63fbdc89917a1a5be127 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 12:54:01 +0300 Subject: [PATCH 80/88] fix: delete extra log --- pkg/ssh/config_parse.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index de5ab99..c787281 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -155,7 +155,6 @@ func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { for s.Scan() { line := strings.TrimSpace(s.Text()) - fmt.Println(line) switch { case isComment(line): @@ -175,6 +174,5 @@ func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { return nil, err } - fmt.Println(connections) return connections, nil } From b9d7a41811089f5c2144ab99e9fa4a80fc48a13e Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 20:53:09 +0300 Subject: [PATCH 81/88] feat: add logic for save connection --- pkg/ssh/config.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 102c756..3bc6ada 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -1,7 +1,6 @@ package ssh import ( - "bufio" "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" @@ -26,11 +25,16 @@ func (c *Config) GetConnections() (*connect.Connections, error) { return nil, err } - return parseConnections(bufio.NewScanner(file)) + return parseConnections(file) } func (c *Config) SaveConnection(connection *connect.Connect) error { - return nil + file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_WRONLY|os.O_APPEND|os.O_CREATE) + if err != nil { + return err + } + + return addConnection(connection, file) } func (c *Config) UpdateConnection(connection *connect.Connect) (*connect.Connect, error) { From 318c45a60b6fdac17accde60458e96fe28a472fb Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 20:53:32 +0300 Subject: [PATCH 82/88] feat: add logic for add connection --- pkg/ssh/config_parse.go | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index c787281..55b6424 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/misha-ssh/kernel/internal/logger" "github.com/misha-ssh/kernel/pkg/connect" ) @@ -149,7 +150,16 @@ func isConnectionEmpty(connection *connect.Connect) bool { connection.PrivateKey == "" } -func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { +func parseConnections(file *os.File) (*connect.Connections, error) { + defer func() { + err := file.Close() + if err != nil { + logger.Error(err.Error()) + } + }() + + s := bufio.NewScanner(file) + connections := new(connect.Connections) current := new(connect.Connect) @@ -176,3 +186,31 @@ func parseConnections(s *bufio.Scanner) (*connect.Connections, error) { return connections, nil } + +func prepareConnection(connection *connect.Connect) string { + var configConnection string + + configConnection += fmt.Sprintf("\n\nHost %v", connection.Alias) + configConnection += fmt.Sprintf("\n\tHostName %v", connection.Address) + configConnection += fmt.Sprintf("\n\tUser %v", connection.Login) + configConnection += fmt.Sprintf("\n\tPort %v", strconv.Itoa(connection.Port)) + configConnection += fmt.Sprintf("\n\tIdentityFile %v", connection.PrivateKey) + + return configConnection +} + +func addConnection(connection *connect.Connect, file *os.File) error { + defer func() { + err := file.Close() + if err != nil { + logger.Error(err.Error()) + } + }() + + if err := connection.Validate(); err != nil { + return err + } + + _, err := file.WriteString(prepareConnection(connection)) + return err +} From c21647391e915a9455fa006b3c69659724d31962 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 30 Nov 2025 20:53:41 +0300 Subject: [PATCH 83/88] feat: add new test --- pkg/ssh/config_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index d722338..24b6116 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -1,9 +1,13 @@ package ssh import ( + "bufio" + "fmt" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/testutil" "github.com/stretchr/testify/require" + "os" + "path/filepath" "testing" "github.com/misha-ssh/kernel/pkg/connect" @@ -71,3 +75,88 @@ func TestConfig_GetConnections(t *testing.T) { }) } } + +func TestConfig_SaveConnection(t *testing.T) { + tests := []struct { + name string + connection *connect.Connect + filename string + wantErr bool + }{ + { + name: "success - add connections", + connection: &connect.Connect{ + Alias: "new", + Address: "localhost", + Login: "user", + Port: 3333, + PrivateKey: "testdata/private_key", + }, + filename: "testdata/config", + wantErr: false, + }, + { + name: "success - empty file", + connection: &connect.Connect{ + Alias: "test", + Address: "localhost", + Login: "user", + Port: 3333, + PrivateKey: "testdata/private_key", + }, + filename: "testdata/empty_config", + wantErr: false, + }, + { + name: "fail - exists alias", + connection: &connect.Connect{ + Alias: "test", + Address: "localhost", + Login: "user", + Port: 3333, + PrivateKey: "testdata/private_key", + }, + filename: "testdata/config", + wantErr: true, + }, + { + name: "fail - invalid connection", + connection: &connect.Connect{ + Alias: "test", + }, + filename: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + + if tt.filename != "" { + err := testutil.CreateSSHConfig(tmpDir, tt.filename) + require.NoError(t, err) + } + + config := &Config{ + LocalStorage: &storage.Local{ + Path: tmpDir, + }, + } + + err := config.SaveConnection(tt.connection) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + // todo delete before finished method + file, err := os.OpenFile(filepath.Join(tmpDir, "config"), os.O_RDONLY, 0) + s := bufio.NewScanner(file) + + for s.Scan() { + fmt.Println(s.Text()) + } + }) + } +} From 4678fa2fa45715b6e4b92f58ce0c74a2785c1662 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 1 Dec 2025 00:29:44 +0300 Subject: [PATCH 84/88] fix: update flag --- pkg/ssh/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index 3bc6ada..f11b8d6 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -20,7 +20,7 @@ func NewConfig() *Config { } func (c *Config) GetConnections() (*connect.Connections, error) { - file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) + file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDONLY) if err != nil { return nil, err } @@ -29,7 +29,7 @@ func (c *Config) GetConnections() (*connect.Connections, error) { } func (c *Config) SaveConnection(connection *connect.Connect) error { - file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_WRONLY|os.O_APPEND|os.O_CREATE) + file, err := c.LocalStorage.GetOpenFile(envconst.FilenameConfigSSH, os.O_RDWR) if err != nil { return err } From 9b54aa2237728b6e7136107be45a105568db2102 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 1 Dec 2025 00:36:09 +0300 Subject: [PATCH 85/88] fix: add logic for check is exists alias --- pkg/ssh/config_parse.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/ssh/config_parse.go b/pkg/ssh/config_parse.go index 55b6424..446fee0 100644 --- a/pkg/ssh/config_parse.go +++ b/pkg/ssh/config_parse.go @@ -211,6 +211,29 @@ func addConnection(connection *connect.Connect, file *os.File) error { return err } + s := bufio.NewScanner(file) + + for s.Scan() { + line := strings.TrimSpace(s.Text()) + + switch { + case isComment(line): + continue + case isEmptyLine(line): + continue + default: + values := strings.Fields(line) + + if len(values) < 2 { + continue + } + + if strings.ToLower(values[0]) == "host" && values[1] == connection.Alias { + return fmt.Errorf("alias already in use") + } + } + } + _, err := file.WriteString(prepareConnection(connection)) return err } From 9770dddf22a4c26aae78fc821691d917fbfe5b99 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 1 Dec 2025 00:36:38 +0300 Subject: [PATCH 86/88] fix: delete extra code --- pkg/ssh/config_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 24b6116..734345e 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -1,13 +1,9 @@ package ssh import ( - "bufio" - "fmt" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/testutil" "github.com/stretchr/testify/require" - "os" - "path/filepath" "testing" "github.com/misha-ssh/kernel/pkg/connect" @@ -149,14 +145,6 @@ func TestConfig_SaveConnection(t *testing.T) { } else { require.NoError(t, err) } - - // todo delete before finished method - file, err := os.OpenFile(filepath.Join(tmpDir, "config"), os.O_RDONLY, 0) - s := bufio.NewScanner(file) - - for s.Scan() { - fmt.Println(s.Text()) - } }) } } From 025fcbbb04c32fbe992e24d9fc675362e05e6137 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 1 Dec 2025 00:38:09 +0300 Subject: [PATCH 87/88] fix: update order pkg --- pkg/ssh/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/ssh/config.go b/pkg/ssh/config.go index f11b8d6..6e80ff7 100644 --- a/pkg/ssh/config.go +++ b/pkg/ssh/config.go @@ -1,10 +1,11 @@ package ssh import ( + "os" + "github.com/misha-ssh/kernel/configs/envconst" "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" - "os" ) type Config struct { From 8616da8ba0831e9ec6199d8ed284177dcc29b12d Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 1 Dec 2025 00:38:13 +0300 Subject: [PATCH 88/88] fix: update order pkg --- pkg/ssh/config_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/ssh/config_test.go b/pkg/ssh/config_test.go index 734345e..c8e6d64 100644 --- a/pkg/ssh/config_test.go +++ b/pkg/ssh/config_test.go @@ -1,12 +1,12 @@ package ssh import ( - "github.com/misha-ssh/kernel/internal/storage" - "github.com/misha-ssh/kernel/testutil" - "github.com/stretchr/testify/require" "testing" + "github.com/misha-ssh/kernel/internal/storage" "github.com/misha-ssh/kernel/pkg/connect" + "github.com/misha-ssh/kernel/testutil" + "github.com/stretchr/testify/require" ) func TestConfig_GetConnections(t *testing.T) {