Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A self-hosted database backup management tool. Schedule automated backups, monit

## Features

- Multiple database support (PostgreSQL, MySQL, MongoDB, Redis)
- Multiple database support (PostgreSQL, MySQL, MSSQL, MongoDB, Redis)
- Automated scheduling with cron syntax
- S3-compatible storage integration
- Built-in backup comparison and diff viewer
Expand Down
14 changes: 13 additions & 1 deletion apps/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ RUN apk add --no-cache \
postgresql-client \
mysql-client \
mongodb-tools \
redis
redis \
freetds

# Install Microsoft SQL Server Tools
RUN apk add --no-cache curl gnupg && \
curl -O https://download.microsoft.com/download/b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486/msodbcsql18_18.1.1.1-1_amd64.apk && \
curl -O https://download.microsoft.com/download/b/9/f/b9f3cce4-3925-46d4-9f46-da08869c6486/mssql-tools18_18.1.1.1-1_amd64.apk && \
apk add --allow-untrusted msodbcsql18_18.1.1.1-1_amd64.apk && \
apk add --allow-untrusted mssql-tools18_18.1.1.1-1_amd64.apk && \
rm -f msodbcsql18_18.1.1.1-1_amd64.apk mssql-tools18_18.1.1.1-1_amd64.apk && \
apk del curl gnupg

ENV PATH="${PATH}:/opt/mssql-tools18/bin"

WORKDIR /app

Expand Down
3 changes: 3 additions & 0 deletions apps/api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ require (

require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/denisenkom/go-mssqldb v0.12.3 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/minio/crc64nvme v1.0.2 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
Expand Down
23 changes: 23 additions & 0 deletions apps/api/go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
Expand All @@ -16,6 +23,10 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down Expand Up @@ -44,10 +55,12 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -60,6 +73,8 @@ github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
Expand All @@ -77,13 +92,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
Expand All @@ -93,6 +111,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -118,5 +137,9 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
38 changes: 38 additions & 0 deletions apps/api/internal/backup/backup_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var requiredTools = map[string]string{
"mariadb": "mysqldump",
"mongodb": "mongodump",
"redis": "redis-cli",
"mssql": "sqlcmd",
}

func (s *BackupService) verifyBackupTools(dbType string) error {
Expand Down Expand Up @@ -159,3 +160,40 @@ func (s *BackupService) createRedisDumpCmd(conn *connection.StoredConnection, ou

return exec.Command(binPath, args...)
}

func (s *BackupService) createMSSQLDumpCmd(conn *connection.StoredConnection, outputPath string) *exec.Cmd {
binaryPath := s.findDatabaseBinaryPath("mssql")
if binaryPath == "" {
fmt.Printf("ERROR: sqlcmd binary not found. Please install SQL Server command-line tools.\n")
return nil
}

binPath := filepath.Join(binaryPath, common.GetPlatformExecutableName(requiredTools["mssql"]))

scriptPath := outputPath + ".sql"
backupScript := fmt.Sprintf(`
BACKUP DATABASE [%s]
TO DISK = N'%s'
WITH FORMAT, COMPRESSION, STATS = 10;
GO
`, conn.DatabaseName, outputPath)

if err := os.WriteFile(scriptPath, []byte(backupScript), 0644); err != nil {
fmt.Printf("ERROR: Failed to create backup script: %v\n", err)
return nil
}

args := []string{
"-S", fmt.Sprintf("%s,%d", conn.Host, conn.Port),
"-U", conn.Username,
"-P", conn.Password,
"-d", "master",
"-i", scriptPath,
}

if conn.SSL {
args = append(args, "-N")
}

return exec.Command(binPath, args...)
}
70 changes: 70 additions & 0 deletions apps/api/internal/backup/backup_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var restoreTools = map[string]string{
"mysql": "mysql",
"mariadb": "mysql",
"mongodb": "mongorestore",
"mssql": "sqlcmd",
}

// RestoreBackup restores a backup to a target database connection
Expand Down Expand Up @@ -61,6 +62,8 @@ func (s *BackupService) RestoreBackup(backupID string, connectionID string) erro
cmd = s.createMySQLRestoreCmd(conn, backup.Path)
case "mongodb":
cmd = s.createMongoRestoreCmd(conn, backup.Path)
case "mssql":
cmd = s.createMSSQLRestoreCmd(conn, backup.Path)
default:
return fmt.Errorf("unsupported database type for restore: %s", conn.Type)
}
Expand All @@ -81,6 +84,8 @@ func (s *BackupService) validateRestoreOutput(dbType, dbName string, output []by
return s.validateMySQLRestore(dbName, output, cmdErr)
case "mongodb":
return s.validateMongoDBRestore(dbName, output, cmdErr)
case "mssql":
return s.validateMSSQLRestore(dbName, output, cmdErr)
default:
return fmt.Errorf("unsupported database type: %s", dbType)
}
Expand Down Expand Up @@ -136,6 +141,28 @@ func (s *BackupService) validateMongoDBRestore(dbName string, output []byte, cmd
return nil
}

func (s *BackupService) validateMSSQLRestore(dbName string, output []byte, cmdErr error) error {
outputStr := string(output)

if strings.Contains(outputStr, "Msg") && strings.Contains(outputStr, "Level 16") {
return fmt.Errorf("MSSQL restore failed with error: %s", outputStr)
}

if cmdErr != nil {
return fmt.Errorf("MSSQL restore command failed: %v, output: %s", cmdErr, outputStr)
}

if strings.Contains(outputStr, "RESTORE DATABASE successfully processed") {
return nil
}

if cmdErr != nil {
return fmt.Errorf("MSSQL restore may have failed: %s", outputStr)
}

return nil
}

func isCriticalPostgreSQLError(line string) bool {
nonCriticalPatterns := []string{
"WARNING:",
Expand Down Expand Up @@ -246,3 +273,46 @@ func (s *BackupService) createMongoRestoreCmd(conn *connection.StoredConnection,

return exec.Command(binPath, args...)
}

func (s *BackupService) createMSSQLRestoreCmd(conn *connection.StoredConnection, backupPath string) *exec.Cmd {
binaryPath := s.findDatabaseRestorePath("mssql")
if binaryPath == "" {
fmt.Printf("ERROR: sqlcmd binary not found. Please install SQL Server command-line tools.\n")
return nil
}

binPath := filepath.Join(binaryPath, common.GetPlatformExecutableName(restoreTools["mssql"]))

scriptPath := backupPath + "_restore.sql"
restoreScript := fmt.Sprintf(`
USE master;
GO
ALTER DATABASE [%s] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO
RESTORE DATABASE [%s]
FROM DISK = N'%s'
WITH REPLACE, STATS = 10;
GO
ALTER DATABASE [%s] SET MULTI_USER;
GO
`, conn.DatabaseName, conn.DatabaseName, backupPath, conn.DatabaseName)

if err := os.WriteFile(scriptPath, []byte(restoreScript), 0644); err != nil {
fmt.Printf("ERROR: Failed to create restore script: %v\n", err)
return nil
}

args := []string{
"-S", fmt.Sprintf("%s,%d", conn.Host, conn.Port),
"-U", conn.Username,
"-P", conn.Password,
"-d", "master",
"-i", scriptPath,
}

if conn.SSL {
args = append(args, "-N")
}

return exec.Command(binPath, args...)
}
2 changes: 2 additions & 0 deletions apps/api/internal/backup/backup_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ func (s *BackupService) createMultiDatabaseBackup(conn *connection.StoredConnect
cmd = s.createMongoDumpCmd(&tempConn, backupPath)
case "redis":
cmd = s.createRedisDumpCmd(&tempConn, backupPath)
case "mssql":
cmd = s.createMSSQLDumpCmd(&tempConn, backupPath)
default:
return nil, fmt.Errorf("unsupported database type for backup: %s", conn.Type)
}
Expand Down
29 changes: 29 additions & 0 deletions apps/api/internal/connection/connection_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"database/sql"
"fmt"

_ "github.com/denisenkom/go-mssqldb"
"github.com/go-sql-driver/mysql"
"github.com/lib/pq"
"github.com/mattn/go-sqlite3"
Expand Down Expand Up @@ -39,6 +40,8 @@ func (cm *ConnectionManager) Connect(config ConnectionConfig) error {
return cm.connectMongoDB(config)
case "redis":
return cm.connectRedis(config)
case "mssql":
return cm.connectMSSQL(config)
default:
return fmt.Errorf("unsupported database type: %s", config.Type)
}
Expand Down Expand Up @@ -76,6 +79,8 @@ func (cm *ConnectionManager) connectWithSSH(config ConnectionConfig) error {
connErr = cm.connectMongoDB(tunnelConfig)
case "redis":
connErr = cm.connectRedis(tunnelConfig)
case "mssql":
connErr = cm.connectMSSQL(tunnelConfig)
default:
tunnel.Stop()
return fmt.Errorf("unsupported database type: %s", config.Type)
Expand Down Expand Up @@ -205,6 +210,30 @@ func (cm *ConnectionManager) connectRedis(config ConnectionConfig) error {
return nil
}

func (cm *ConnectionManager) connectMSSQL(config ConnectionConfig) error {
dsn := fmt.Sprintf("server=%s;port=%d;user id=%s;password=%s;database=%s",
config.Host, config.Port, config.Username, config.Password, config.Database)

if config.SSL {
dsn += ";encrypt=true;TrustServerCertificate=false"
} else {
dsn += ";encrypt=disable"
}

db, err := sql.Open("sqlserver", dsn)
if err != nil {
return fmt.Errorf("failed to open MSSQL connection: %w", err)
}

if err := db.Ping(); err != nil {
db.Close()
return fmt.Errorf("failed to ping MSSQL: %w", err)
}

cm.connections[config.ID] = db
return nil
}

func (cm *ConnectionManager) Disconnect(id string) error {
conn, exists := cm.connections[id]
if !exists {
Expand Down
6 changes: 5 additions & 1 deletion apps/web/components/views/connections/connection-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export function ConnectionForm({ onSuccess, onCancel }: ConnectionFormProps) {
'mongo': 'mongodb',
'redis': 'redis',
'rediss': 'redis',
'mssql': 'mssql',
'sqlserver': 'mssql',
};

const mappedType = typeMapping[type];
Expand All @@ -74,7 +76,7 @@ export function ConnectionForm({ onSuccess, onCancel }: ConnectionFormProps) {
toast({
variant: "destructive",
title: "Unsupported Database Type",
description: `The database type "${type}" is not supported. Supported types: PostgreSQL, MySQL, MongoDB, Redis`,
description: `The database type "${type}" is not supported. Supported types: PostgreSQL, MySQL, MongoDB, Redis, MSSQL`,
});
return false;
}
Expand Down Expand Up @@ -118,6 +120,7 @@ export function ConnectionForm({ onSuccess, onCancel }: ConnectionFormProps) {
'mysql': 3306,
'mongodb': 27017,
'redis': 6379,
'mssql': 1433,
};
return ports[type] || 5432;
};
Expand Down Expand Up @@ -251,6 +254,7 @@ export function ConnectionForm({ onSuccess, onCancel }: ConnectionFormProps) {
<SelectContent>
<SelectItem value="postgresql">PostgreSQL</SelectItem>
<SelectItem value="mysql">MySQL</SelectItem>
<SelectItem value="mssql">MSSQL</SelectItem>
<SelectSeparator />
<SelectItem value="mongodb">MongoDB</SelectItem>
<SelectItem value="redis">Redis</SelectItem>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/types/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ export const statusColors: Record<StatusColor, string> = {
running: "bg-blue-500/15 text-blue-500 border-blue-500/20",
};

export type DatabaseType = 'mysql' | 'postgresql' | 'mongodb' | 'redis';
export type DatabaseType = 'mysql' | 'postgresql' | 'mongodb' | 'redis' | 'mssql';

export const typeLabels: Record<DatabaseType, string> = {
mysql: 'MySQL',
postgresql: 'PostgreSQL',
mongodb: 'MongoDB',
redis: 'Redis',
mssql: 'MSSQL',
} as const;
Loading
Loading