From 675ecb55f7456f8ef0e4afad3219e2ef61081b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiger=20Wang=20=28=E7=8E=8B=E8=B1=AB=29?= Date: Sun, 28 Aug 2022 20:40:51 -0400 Subject: [PATCH] add migration logic for 0.2.x (#8) --- .../service.d/user-service/migration.list | 4 +- .../etc/casaos/user-service.conf.sample | 2 +- cmd/migration-tool/main.go | 11 +- cmd/migration-tool/migration-033-034-035.go | 96 ------- cmd/migration-tool/migration_032_and_older.go | 191 ++++++++++++++ cmd/migration-tool/migration_033_034_035.go | 239 ++++++++++++++++++ common/version.go | 2 +- go.mod | 4 +- go.sum | 4 +- main.go | 2 +- 10 files changed, 445 insertions(+), 110 deletions(-) delete mode 100644 cmd/migration-tool/migration-033-034-035.go create mode 100644 cmd/migration-tool/migration_032_and_older.go create mode 100644 cmd/migration-tool/migration_033_034_035.go diff --git a/build/scripts/migration/service.d/user-service/migration.list b/build/scripts/migration/service.d/user-service/migration.list index 7325ac3..0b2bdfa 100644 --- a/build/scripts/migration/service.d/user-service/migration.list +++ b/build/scripts/migration/service.d/user-service/migration.list @@ -1,2 +1,2 @@ -LEGACY_WITHOUT_VERSION v0.3.6-alpha2 -v0.3.5 v0.3.6-alpha2 +LEGACY_WITHOUT_VERSION v0.3.6-alpha3 +v0.3.5 v0.3.6-alpha3 diff --git a/build/sysroot/etc/casaos/user-service.conf.sample b/build/sysroot/etc/casaos/user-service.conf.sample index a146555..1d2d1ba 100644 --- a/build/sysroot/etc/casaos/user-service.conf.sample +++ b/build/sysroot/etc/casaos/user-service.conf.sample @@ -5,5 +5,5 @@ RuntimePath=/var/run/casaos LogPath = /var/log/casaos LogSaveName = user-service LogFileExt = log -DBPath = /var/lib/casaos +DBPath = /var/lib/casaos/db UserDataPath = /var/lib/casaos diff --git a/cmd/migration-tool/main.go b/cmd/migration-tool/main.go index 62bcb30..3e9271e 100644 --- a/cmd/migration-tool/main.go +++ b/cmd/migration-tool/main.go @@ -40,19 +40,20 @@ func main() { } if !*forceFlag { - serviceEnabled, err := systemctl.IsServiceEnabled(userServiceName) + isRunning, err := systemctl.IsServiceRunning(userServiceName) if err != nil { - _logger.Error("Failed to check if %s is enabled", userServiceName) + _logger.Error("Failed to check if %s is running", userServiceName) panic(err) } - if serviceEnabled { - _logger.Info("%s is already enabled. If migration is still needed, try with -f.", userServiceName) + if isRunning { + _logger.Info("%s is running. If migration is still needed, try with -f.", userServiceName) os.Exit(1) } } migrationTools := []interfaces.MigrationTool{ + NewMigrationToolFor032AndOlder(), NewMigrationToolFor033_034_035(), } @@ -85,6 +86,6 @@ func main() { } if err := selectedMigrationTool.PostMigrate(); err != nil { - panic(err) + _logger.Error("Migration succeeded, but post-migration failed: %s", err) } } diff --git a/cmd/migration-tool/migration-033-034-035.go b/cmd/migration-tool/migration-033-034-035.go deleted file mode 100644 index e7c83df..0000000 --- a/cmd/migration-tool/migration-033-034-035.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - interfaces "github.com/IceWhaleTech/CasaOS-Common" - "github.com/IceWhaleTech/CasaOS-Common/utils/version" - "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" - "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/file" - "gopkg.in/ini.v1" -) - -type migrationTool struct{} - -func (u *migrationTool) IsMigrationNeeded() (bool, error) { - _logger.Info("Checking if migration is needed for CasaoS version between 0.3.3 and 0.3.5...") - - minorVersion, err := version.DetectMinorVersion() - if err != nil { - return false, err - } - - if minorVersion != 3 { - return false, nil - } - - // this is the best way to tell if CasaOS version is between 0.3.3 and 0.3.5 - isUserDataInDatabase, err := version.IsUserDataInDatabase() - if err != nil { - return false, err - } - - if !isUserDataInDatabase { - return false, nil - } - - return true, nil -} - -func (u *migrationTool) PreMigrate() error { - _logger.Info("Copying %s to %s if it doesn't exist...", userServiceConfigSampleFilePath, config.UserServiceConfigFilePath) - if err := file.CopySingleFile(userServiceConfigSampleFilePath, config.UserServiceConfigFilePath, "skip"); err != nil { - return err - } - return nil -} - -func (u *migrationTool) Migrate() error { - _logger.Info("Loading legacy %s...", version.LegacyCasaOSConfigFilePath) - legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) - if err != nil { - return err - } - - // LogPath - logPath, err := legacyConfigFile.Section("app").GetKey("LogPath") - if err != nil { - return err - } - - // LogFileExt - logFileExt, err := legacyConfigFile.Section("app").GetKey("LogFileExt") - if err != nil { - return err - } - - // DBPath - dbPath, err := legacyConfigFile.Section("app").GetKey("DBPath") - if err != nil { - return err - } - - // UserDataPath - userDataPath, err := legacyConfigFile.Section("app").GetKey("UserDataPath") - if err != nil { - return err - } - - _logger.Info("Updating %s with settings from legacy configuration...", config.UserServiceConfigFilePath) - config.InitSetup(config.UserServiceConfigFilePath) - - config.AppInfo.LogPath = logPath.Value() - config.AppInfo.LogFileExt = logFileExt.Value() - config.AppInfo.DBPath = dbPath.Value() - config.AppInfo.UserDataPath = userDataPath.Value() - - config.SaveSetup(config.UserServiceConfigFilePath) - - return nil -} - -func (u *migrationTool) PostMigrate() error { - return nil -} - -func NewMigrationToolFor033_034_035() interfaces.MigrationTool { - return &migrationTool{} -} diff --git a/cmd/migration-tool/migration_032_and_older.go b/cmd/migration-tool/migration_032_and_older.go new file mode 100644 index 0000000..3136cf9 --- /dev/null +++ b/cmd/migration-tool/migration_032_and_older.go @@ -0,0 +1,191 @@ +package main + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "time" + + interfaces "github.com/IceWhaleTech/CasaOS-Common" + "github.com/IceWhaleTech/CasaOS-Common/utils/version" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/sqlite" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/encryption" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/file" + "github.com/IceWhaleTech/CasaOS-UserService/service" + "github.com/IceWhaleTech/CasaOS-UserService/service/model" + "gopkg.in/ini.v1" +) + +type migrationTool1 struct{} + +func (u *migrationTool1) IsMigrationNeeded() (bool, error) { + if _, err := os.Stat(version.LegacyCasaOSConfigFilePath); err != nil { + _logger.Info("`%s` not found, migration is not needed.", version.LegacyCasaOSConfigFilePath) + return false, nil + } + + majorVersion, minorVersion, patchVersion, err := version.DetectLegacyVersion() + if err != nil { + if err == version.ErrLegacyVersionNotFound { + return false, nil + } + + return false, err + } + + if majorVersion != 0 { + return false, nil + } + + if minorVersion == 2 { + _logger.Info("Migration is needed for a CasaOS version 0.2.x...") + return true, nil + } + + if minorVersion == 3 && patchVersion < 2 { + _logger.Info("Migration is needed for a CasaOS version between 0.3.0 and 0.3.2...") + return true, nil + } + + return false, nil +} + +func (u *migrationTool1) PreMigrate() error { + _logger.Info("Copying %s to %s if it doesn't exist...", userServiceConfigSampleFilePath, config.UserServiceConfigFilePath) + if err := file.CopySingleFile(userServiceConfigSampleFilePath, config.UserServiceConfigFilePath, "skip"); err != nil { + return err + } + + extension := "." + time.Now().Format("20060102") + ".bak" + + _logger.Info("Creating a backup %s if it doesn't exist...", version.LegacyCasaOSConfigFilePath+extension) + return file.CopySingleFile(version.LegacyCasaOSConfigFilePath, version.LegacyCasaOSConfigFilePath+extension, "skip") +} + +func (u *migrationTool1) Migrate() error { + _logger.Info("Loading legacy %s...", version.LegacyCasaOSConfigFilePath) + legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) + if err != nil { + return err + } + + migrateConfigurationFile1(legacyConfigFile) + + return migrateUser1(legacyConfigFile) +} + +func (u *migrationTool1) PostMigrate() error { + _logger.Info("Deleting legacy `user` section in %s...", version.LegacyCasaOSConfigFilePath) + + legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) + if err != nil { + return err + } + + legacyConfigFile.DeleteSection("user") + + return legacyConfigFile.SaveTo(version.LegacyCasaOSConfigFilePath) +} + +func NewMigrationToolFor032AndOlder() interfaces.MigrationTool { + return &migrationTool1{} +} + +func migrateConfigurationFile1(legacyConfigFile *ini.File) { + _logger.Info("Updating %s with settings from legacy configuration...", config.UserServiceConfigFilePath) + config.InitSetup(config.UserServiceConfigFilePath) + + // LogPath + if logPath, err := legacyConfigFile.Section("app").GetKey("LogPath"); err == nil { + _logger.Info("[app] LogPath = %s", logPath.Value()) + config.AppInfo.LogPath = logPath.Value() + } + + if logPath, err := legacyConfigFile.Section("app").GetKey("LogSavePath"); err == nil { + _logger.Info("[app] LogSavePath = %s", logPath.Value()) + config.AppInfo.LogPath = logPath.Value() + } + + // LogFileExt + if logFileExt, err := legacyConfigFile.Section("app").GetKey("LogFileExt"); err == nil { + _logger.Info("[app] LogFileExt = %s", logFileExt.Value()) + config.AppInfo.LogFileExt = logFileExt.Value() + } + + // UserDataPath + if userDataPath, err := legacyConfigFile.Section("app").GetKey("UserDataPath"); err == nil { + _logger.Info("[app] UserDataPath = %s", userDataPath.Value()) + config.AppInfo.UserDataPath = userDataPath.Value() + } + + _logger.Info("Saving %s...", config.UserServiceConfigFilePath) + config.SaveSetup(config.UserServiceConfigFilePath) +} + +func migrateUser1(legacyConfigFile *ini.File) error { + _logger.Info("Migrating user from configuration file to database...") + + user := model.UserDBModel{Role: "admin"} + + // UserName + if userName, err := legacyConfigFile.Section("user").GetKey("UserName"); err == nil { + _logger.Info("[user] UserName = %s", userName.Value()) + user.Username = userName.Value() + } + + // Email + if userEmail, err := legacyConfigFile.Section("user").GetKey("Email"); err == nil { + _logger.Info("[user] Email = %s", userEmail.Value()) + user.Email = userEmail.Value() + } + + // NickName + if userNickName, err := legacyConfigFile.Section("user").GetKey("NickName"); err == nil { + _logger.Info("[user] NickName = %s", userNickName.Value()) + user.Nickname = userNickName.Value() + } + + // Password + if userPassword, err := legacyConfigFile.Section("user").GetKey("PWD"); err == nil { + _logger.Info("[user] Password = %s", strings.Repeat("*", len(userPassword.Value()))) + user.Password = encryption.GetMD5ByStr(userPassword.Value()) + } + + newDB := sqlite.GetDb(config.AppInfo.DBPath) + userService := service.NewUserService(newDB) + + if len(user.Username) == 0 { + _logger.Info("No user found in legacy configuration file. Skipping...") + return nil + } + + if userService.GetUserInfoByUserName(user.Username).Id > 0 { + _logger.Info("User `%s` already exists in user database at %s. Skipping...", user.Username, config.AppInfo.DBPath) + return nil + } + + _logger.Info("Creating user %s in database at %s...", user.Username, config.AppInfo.DBPath) + user = userService.CreateUser(user) + if user.Id > 0 { + userPath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + _logger.Info("Creating user data path: %s", userPath) + if err := file.MkDir(userPath); err != nil { + return err + } + + if legacyProjectPath, err := legacyConfigFile.Section("app").GetKey("ProjectPath"); err == nil { + appOrderJSONFile := filepath.Join(legacyProjectPath.Value(), "app_order.json") + + if _, err := os.Stat(appOrderJSONFile); err == nil { + _logger.Info("Moving %s to %s...", appOrderJSONFile, userPath) + if err := os.Rename(appOrderJSONFile, filepath.Join(userPath, "app_order.json")); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/cmd/migration-tool/migration_033_034_035.go b/cmd/migration-tool/migration_033_034_035.go new file mode 100644 index 0000000..97bf9bb --- /dev/null +++ b/cmd/migration-tool/migration_033_034_035.go @@ -0,0 +1,239 @@ +package main + +import ( + "database/sql" + "os" + "path/filepath" + "time" + + interfaces "github.com/IceWhaleTech/CasaOS-Common" + "github.com/IceWhaleTech/CasaOS-Common/utils/version" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/sqlite" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/file" + "github.com/IceWhaleTech/CasaOS-UserService/service" + "github.com/IceWhaleTech/CasaOS-UserService/service/model" + "gopkg.in/ini.v1" +) + +type migrationTool2 struct{} + +func (u *migrationTool2) IsMigrationNeeded() (bool, error) { + if _, err := os.Stat(version.LegacyCasaOSConfigFilePath); err != nil { + _logger.Info("`%s` not found, migration is not needed.", version.LegacyCasaOSConfigFilePath) + return false, nil + } + + majorVersion, minorVersion, patchVersion, err := version.DetectLegacyVersion() + if err != nil { + if err == version.ErrLegacyVersionNotFound { + return false, nil + } + + return false, err + } + + if majorVersion != 0 { + return false, nil + } + + if minorVersion != 3 { + return false, nil + } + + if patchVersion < 3 || patchVersion > 5 { + return false, nil + } + + // legacy version has to be between 0.3.3 and 0.3.5 + _logger.Info("Migration is needed for a CasaOS version between 0.3.3 and 0.3.5...") + return true, nil +} + +func (u *migrationTool2) PreMigrate() error { + _logger.Info("Copying %s to %s if it doesn't exist...", userServiceConfigSampleFilePath, config.UserServiceConfigFilePath) + if err := file.CopySingleFile(userServiceConfigSampleFilePath, config.UserServiceConfigFilePath, "skip"); err != nil { + return err + } + + extension := "." + time.Now().Format("20060102") + ".bak" + + _logger.Info("Creating a backup %s if it doesn't exist...", version.LegacyCasaOSConfigFilePath+extension) + if err := file.CopySingleFile(version.LegacyCasaOSConfigFilePath, version.LegacyCasaOSConfigFilePath+extension, "skip"); err != nil { + return err + } + + legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) + if err != nil { + return err + } + + dbPath := legacyConfigFile.Section("app").Key("DBPath").String() + + dbFile := filepath.Join(dbPath, "db", "casaOS.db") + + _logger.Info("Creating a backup %s if it doesn't exist...", dbFile+extension) + if err := file.CopySingleFile(dbFile, dbFile+extension, "skip"); err != nil { + return err + } + + return nil +} + +func (u *migrationTool2) Migrate() error { + _logger.Info("Loading legacy %s...", version.LegacyCasaOSConfigFilePath) + legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) + if err != nil { + return err + } + + migrateConfigurationFile2(legacyConfigFile) + + return migrateUser2(legacyConfigFile) +} + +func (u *migrationTool2) PostMigrate() error { + legacyConfigFile, err := ini.Load(version.LegacyCasaOSConfigFilePath) + if err != nil { + return err + } + + dbPath := legacyConfigFile.Section("app").Key("DBPath").String() + + dbFile := filepath.Join(dbPath, "db", "casaOS.db") + + if _, err := os.Stat(dbFile); err != nil { + return err + } + + legacyDB, err := sql.Open("sqlite3", dbFile) + if err != nil { + return err + } + + defer legacyDB.Close() + + if tableExists, err := isTableExist(legacyDB, "o_users"); err != nil { + return err + } else if tableExists { + _logger.Info("Dropping `o_users` table in legacy database...") + + if _, err = legacyDB.Exec("DROP TABLE o_users"); err != nil { + _logger.Error("Failed to drop `o_users` table in legacy database: %s", err) + } + } + return nil +} + +func NewMigrationToolFor033_034_035() interfaces.MigrationTool { + return &migrationTool2{} +} + +func migrateConfigurationFile2(legacyConfigFile *ini.File) { + _logger.Info("Updating %s with settings from legacy configuration...", config.UserServiceConfigFilePath) + config.InitSetup(config.UserServiceConfigFilePath) + + // LogPath + if logPath, err := legacyConfigFile.Section("app").GetKey("LogPath"); err == nil { + _logger.Info("[app] LogPath = %s", logPath.Value()) + config.AppInfo.LogPath = logPath.Value() + } + + // LogFileExt + if logFileExt, err := legacyConfigFile.Section("app").GetKey("LogFileExt"); err == nil { + _logger.Info("[app] LogFileExt = %s", logFileExt.Value()) + config.AppInfo.LogFileExt = logFileExt.Value() + } + + // DBPath + if dbPath, err := legacyConfigFile.Section("app").GetKey("DBPath"); err == nil { + _logger.Info("[app] DBPath = %s", dbPath.Value()) + config.AppInfo.DBPath = dbPath.Value() + "/db" + } + + // UserDataPath + if userDataPath, err := legacyConfigFile.Section("app").GetKey("UserDataPath"); err == nil { + _logger.Info("[app] UserDataPath = %s", userDataPath.Value()) + config.AppInfo.UserDataPath = userDataPath.Value() + } + + _logger.Info("Saving %s...", config.UserServiceConfigFilePath) + config.SaveSetup(config.UserServiceConfigFilePath) +} + +func migrateUser2(legacyConfigFile *ini.File) error { + _logger.Info("Migrating user from legacy database to user database...") + + user := model.UserDBModel{Role: "admin"} + + dbPath := legacyConfigFile.Section("app").Key("DBPath").String() + + dbFile := filepath.Join(dbPath, "db", "casaOS.db") + + if _, err := os.Stat(dbFile); err != nil { + return err + } + + legacyDB, err := sql.Open("sqlite3", dbFile) + if err != nil { + return err + } + + defer legacyDB.Close() + + if tableExists, err := isTableExist(legacyDB, "o_users"); err != nil { + return err + } else if !tableExists { + _logger.Info("Table `o_users` not found in legacy database. Skipping...") + return nil + } + + sqlStatement := "SELECT id, username, password, role, email, nickname, avatar, description, created_at FROM o_users ORDER BY id ASC" + + rows, err := legacyDB.Query(sqlStatement) + if err != nil { + return err + } + + defer rows.Close() + + newDB := sqlite.GetDb(config.AppInfo.DBPath) + userService := service.NewUserService(newDB) + + for rows.Next() { + if err := rows.Scan( + &user.Id, + &user.Username, + &user.Password, + &user.Role, + &user.Email, + &user.Nickname, + &user.Avatar, + &user.Description, + &user.CreatedAt, + ); err != nil { + return err + } + + if userService.GetUserAllInfoByName(user.Username).Id > 0 { + _logger.Info("User %s already exists in user database at %s, skipping...", user.Username, config.AppInfo.DBPath) + continue + } + + _logger.Info("Creating user %s in user database...", user.Username) + user = userService.CreateUser(user) + } + + return nil +} + +func isTableExist(legacyDB *sql.DB, tableName string) (bool, error) { + rows, err := legacyDB.Query("SELECT name FROM sqlite_master WHERE type='table' AND name= ?", tableName) + if err != nil { + return false, err + } + + defer rows.Close() + + return rows.Next(), nil +} diff --git a/common/version.go b/common/version.go index f3f9408..6564be8 100644 --- a/common/version.go +++ b/common/version.go @@ -1,3 +1,3 @@ package common -const Version = "0.3.5" +const Version = "0.3.6" diff --git a/go.mod b/go.mod index 05836d3..4a4e818 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/IceWhaleTech/CasaOS-UserService -go 1.18 +go 1.19 require ( - github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220825191226-4519f1880f68 + github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220829003259-cf7e89c30d78 github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b github.com/gin-contrib/gzip v0.0.6 github.com/gin-gonic/gin v1.8.1 diff --git a/go.sum b/go.sum index 9c54ed1..a0be9aa 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220825191226-4519f1880f68 h1:KMFS0/08mKgZqsM8fd5If/1NOLwtIvi7dljFEkJCdSw= -github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220825191226-4519f1880f68/go.mod h1:XGqdbedN9UlF3/rylcXKJ2BW4ayugBmEMa4Z0tk2KbQ= +github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220829003259-cf7e89c30d78 h1:Cxy+bTjqpl0txx6gn2/D7O/E6G2SW5c6Ve7RXUin5K4= +github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220829003259-cf7e89c30d78/go.mod h1:5sqNKg5cEH7IUnCklLSTrVoGx1dMBhm9DFDsCYVPvPQ= github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b h1:IiMCqvGelQLGTX151gqVwrzoPQVJy8Q2JAvkhjiQ6tY= github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b/go.mod h1:jcURlZtPPQJJvfIW4ZgDTtpFfak7bPTvKZUxWxf62M8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= diff --git a/main.go b/main.go index e6f68b0..b179b77 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ func init() { logger.LogInit(config.AppInfo.LogPath, config.AppInfo.LogSaveName, config.AppInfo.LogFileExt) if len(*dbFlag) == 0 { - *dbFlag = config.AppInfo.DBPath + "/db" + *dbFlag = config.AppInfo.DBPath } sqliteDB := sqlite.GetDb(*dbFlag)