From 7380c93eb4542959586937788a669104742ccb9f Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Mon, 13 Feb 2023 17:07:28 +0800 Subject: [PATCH 1/4] feat(install): Automatically complete installation based on environment variable. --- go.mod | 4 + go.sum | 10 ++- internal/install/install_from_env.go | 125 +++++++++++++++++++++++++++ internal/install/install_main.go | 5 ++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 internal/install/install_from_env.go diff --git a/go.mod b/go.mod index 0cdb7b76..2a1f59eb 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/swag v1.8.7 + github.com/tidwall/gjson v1.14.4 github.com/yuin/goldmark v1.4.13 golang.org/x/crypto v0.1.0 golang.org/x/net v0.1.0 @@ -109,6 +110,8 @@ require ( github.com/spf13/viper v1.13.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -139,4 +142,5 @@ require ( // github action runner Sometimes it will time out. replace gitee.com/travelliu/dm v1.8.11192 => github.com/aichy126/dm v1.8.11192 + replace modernc.org/z v1.2.19 => github.com/aichy126/modernc.org_z v1.2.19 diff --git a/go.sum b/go.sum index 3a2504b4..2a0f93f0 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/aichy126/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= +github.com/aichy126/modernc.org_z v1.2.19 h1:g4KvQojkFWBJk47OGvKzuudTr5mRF7jORSwobYyc2Sc= +github.com/aichy126/modernc.org_z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -678,6 +680,12 @@ github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9e github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= @@ -1287,8 +1295,6 @@ modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw= modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= -modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/install/install_from_env.go b/internal/install/install_from_env.go new file mode 100644 index 00000000..f519c578 --- /dev/null +++ b/internal/install/install_from_env.go @@ -0,0 +1,125 @@ +package install + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + + "github.com/gin-gonic/gin" + "github.com/tidwall/gjson" +) + +type Env struct { + AutoInstall string `json:"auto_install"` + DbType string `json:"db_type"` + DbUsername string `json:"db_username"` + DbPassword string `json:"db_password"` + DbHost string `json:"db_host"` + DbName string `json:"db_name"` + DbFile string `json:"db_file"` + Language string `json:"lang"` + + SiteName string `json:"site_name"` + SiteURL string `json:"site_url"` + ContactEmail string `json:"contact_email"` + AdminName string `json:"name"` + AdminPassword string `json:"password"` + AdminEmail string `json:"email"` +} + +func TryToInstallByEnv() (installByEnv bool, err error) { + env := loadEnv() + if len(env.AutoInstall) == 0 { + return false, nil + } + fmt.Println("[auto-install] try to install by environment variable") + return true, initByEnv(env) +} + +func loadEnv() (env *Env) { + return &Env{ + AutoInstall: os.Getenv("AUTO_INSTALL"), + DbType: os.Getenv("DB_TYPE"), + DbUsername: os.Getenv("DB_USERNAME"), + DbPassword: os.Getenv("DB_PASSWORD"), + DbHost: os.Getenv("DB_HOST"), + DbName: os.Getenv("DB_NAME"), + DbFile: os.Getenv("DB_FILE"), + Language: os.Getenv("LANGUAGE"), + SiteName: os.Getenv("SITE_NAME"), + SiteURL: os.Getenv("SITE_URL"), + ContactEmail: os.Getenv("CONTACT_EMAIL"), + AdminName: os.Getenv("ADMIN_NAME"), + AdminPassword: os.Getenv("ADMIN_PASSWORD"), + AdminEmail: os.Getenv("ADMIN_EMAIL"), + } +} + +func initByEnv(env *Env) (err error) { + gin.SetMode(gin.TestMode) + if err = dbCheck(env); err != nil { + return err + } + if err = initConfigAndDb(env); err != nil { + return err + } + if err = initBaseInfo(env); err != nil { + return err + } + return nil +} + +func dbCheck(env *Env) (err error) { + req := &CheckDatabaseReq{ + DbType: env.DbType, + DbUsername: env.DbUsername, + DbPassword: env.DbPassword, + DbHost: env.DbHost, + DbName: env.DbName, + DbFile: env.DbFile, + } + return requestAPI(req, "POST", "/installation/db/check", CheckDatabase) +} + +func initConfigAndDb(env *Env) (err error) { + req := &CheckDatabaseReq{ + DbType: env.DbType, + DbUsername: env.DbUsername, + DbPassword: env.DbPassword, + DbHost: env.DbHost, + DbName: env.DbName, + DbFile: env.DbFile, + } + return requestAPI(req, "POST", "/installation/init", InitEnvironment) +} + +func initBaseInfo(env *Env) (err error) { + req := &InitBaseInfoReq{ + Language: env.Language, + SiteName: env.SiteName, + SiteURL: env.SiteURL, + ContactEmail: env.ContactEmail, + AdminName: env.AdminName, + AdminPassword: env.AdminPassword, + AdminEmail: env.AdminEmail, + } + return requestAPI(req, "POST", "/installation/base-info", InitBaseInfo) +} + +func requestAPI(req interface{}, method, url string, handlerFunc gin.HandlerFunc) error { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + body, _ := json.Marshal(req) + c.Request, _ = http.NewRequest(method, url, bytes.NewBuffer(body)) + if method == "POST" { + c.Request.Header.Set("Content-Type", "application/json") + } + handlerFunc(c) + if w.Code != http.StatusOK { + return fmt.Errorf(gjson.Get(w.Body.String(), "msg").String()) + } + return nil +} diff --git a/internal/install/install_main.go b/internal/install/install_main.go index 6586b9d3..dff6ef2a 100644 --- a/internal/install/install_main.go +++ b/internal/install/install_main.go @@ -21,6 +21,11 @@ func Run(configPath string) { panic(err) } + // try to install by env + if installByEnv, err := TryToInstallByEnv(); installByEnv && err != nil { + fmt.Printf("[auto-install] try to init by env fail: %v\n", err) + } + installServer := NewInstallHTTPServer() if len(port) == 0 { port = "80" From 24aaf7f49a16b3453cb90560d8bed130dd656a0d Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Mon, 13 Feb 2023 17:25:37 +0800 Subject: [PATCH 2/4] feat(install): Remove outdated function about reserved username --- internal/cli/install.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/internal/cli/install.go b/internal/cli/install.go index b8ac0422..e2d278f1 100644 --- a/internal/cli/install.go +++ b/internal/cli/install.go @@ -41,7 +41,6 @@ func InstallAllInitialEnvironment(dataDirPath string) { FormatAllPath(dataDirPath) installUploadDir() installI18nBundle() - installReservedUsernames() fmt.Println("install all initial environment done") } @@ -114,16 +113,3 @@ func installI18nBundle() { } } } - -func installReservedUsernames() { - reservedUsernamesJsonFilePath := filepath.Join(ConfigFileDir, DefaultReservedUsernamesConfigFileName) - if !dir.CheckFileExist(reservedUsernamesJsonFilePath) { - err := writer.WriteFile(reservedUsernamesJsonFilePath, string(configs.ReservedUsernames)) - if err != nil { - fmt.Printf("[%s] write file fail: %s\n", DefaultReservedUsernamesConfigFileName, err) - } else { - fmt.Printf("[%s] write file success\n", DefaultReservedUsernamesConfigFileName) - } - return - } -} From 07d9f56975c27c2d17c928854f9e5e686aa7a776 Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Mon, 13 Feb 2023 17:31:49 +0800 Subject: [PATCH 3/4] docs(docker): update docker compose for auto install --- docker-compose.uffizzi.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docker-compose.uffizzi.yml b/docker-compose.uffizzi.yml index f26d2bdc..a6df6e80 100644 --- a/docker-compose.uffizzi.yml +++ b/docker-compose.uffizzi.yml @@ -10,6 +10,20 @@ services: answer: image: "${ANSWER_IMAGE}" + environment: + - AUTO_INSTALL=true + - DB_TYPE=mysql + - DB_USERNAME=root + - DB_PASSWORD=password + - DB_HOST=127.0.0.1:3306 + - DB_NAME=answer + - LANGUAGE=en_US + - SITE_NAME=answer-test + - SITE_URL=http://localhost + - CONTACT_EMAIL=test@answer.com + - ADMIN_NAME=admin123 + - ADMIN_PASSWORD=admin123 + - ADMIN_EMAIL=admin123@admin.com volumes: - answer-data:/data deploy: From 8d757c6c589f1920b6800bef2eb7abd1fff2176c Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Mon, 13 Feb 2023 18:09:55 +0800 Subject: [PATCH 4/4] docs(docker): add healthcheck for docker compose --- docker-compose.uffizzi.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docker-compose.uffizzi.yml b/docker-compose.uffizzi.yml index a6df6e80..3d266286 100644 --- a/docker-compose.uffizzi.yml +++ b/docker-compose.uffizzi.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.9" # uffizzi integration x-uffizzi: @@ -26,6 +26,11 @@ services: - ADMIN_EMAIL=admin123@admin.com volumes: - answer-data:/data + depends_on: + mysql: + condition: service_healthy + links: + - db deploy: resources: limits: @@ -38,6 +43,11 @@ services: - MYSQL_ROOT_PASSWORD=password - MYSQL_USER=mysql - MYSQL_PASSWORD=mysql + healthcheck: + test: [ "CMD", "mysqladmin" ,"ping", "-uroot", "-ppassword" ] + timeout: 20s + retries: 10 + command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--skip-character-set-client-handshake'] volumes: - sql_data:/var/lib/mysql deploy: