diff --git a/Dockerfile b/Dockerfile index 733849fd9..321f8b03d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Stage -FROM golang:1.21 AS builder +FROM golang:1.20 AS builder # Set go mod installation source and proxy ARG GO111MODULE=on @@ -10,8 +10,8 @@ ENV GOPROXY=$GOPROXY # Set up the working directory WORKDIR /openim/openim-server -COPY go.mod go.sum ./ -RUN go mod download +COPY go.mod go.sum go.work go.work.sum ./ +#RUN go mod download # Copy all files to the container ADD . . @@ -27,5 +27,6 @@ WORKDIR ${SERVER_WORKDIR} COPY --from=builder ${OPENIM_SERVER_CMDDIR} /openim/openim-server/scripts COPY --from=builder ${SERVER_WORKDIR}/config /openim/openim-server/config COPY --from=builder ${SERVER_WORKDIR}/_output/bin/platforms /openim/openim-server/_output/bin/platforms +COPY --from=builder ${SERVER_WORKDIR}/_output/bin-tools/platforms /openim/openim-server/_output/bin-tools/platforms CMD ["bash","-c","${OPENIM_SERVER_CMDDIR}/docker_start_all.sh"] diff --git a/docker-compose.yaml b/docker-compose.yaml index d71d4c932..84d478cd7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -100,6 +100,7 @@ services: openim-server: image: ghcr.io/openimsdk/openim-server:latest +# build: . container_name: openim-server volumes: - ./logs:/openim/openim-server/logs @@ -149,7 +150,7 @@ services: # ports: # - 9091:9091 depends_on: - - openim-server + - openim-server command: --web.listen-address=:9091 --config.file="/etc/prometheus/prometheus.yml" network_mode: "host" @@ -169,4 +170,4 @@ services: # container_name: node-exporter # restart: always # ports: - # - "9100:9100" + # - "9100:9100" \ No newline at end of file diff --git a/go.work b/go.work index 09e86f032..757933536 100644 --- a/go.work +++ b/go.work @@ -1,7 +1,8 @@ go 1.20 use ( - . + . + ./tools/component ./tools/infra ./tools/ncpu ) diff --git a/scripts/docker_start_all.sh b/scripts/docker_start_all.sh index f617c5057..24847e21e 100755 --- a/scripts/docker_start_all.sh +++ b/scripts/docker_start_all.sh @@ -30,6 +30,15 @@ need_to_start_server_shell=( ${SCRIPTS_ROOT}/start_cron.sh ) +component_check=start_component_check.sh +chmod +x $SCRIPTS_ROOT/$component_check +$SCRIPTS_ROOT/$component_check +if [ $? -ne 0 ]; then + # Print error message and exit + echo "${BOLD_PREFIX}${RED_PREFIX}Error executing ${component_check}. Exiting...${COLOR_SUFFIX}" + exit -1 +fi + #fixme The 10 second delay to start the project is for the docker-compose one-click to start openIM when the infrastructure dependencies are not started sleep 10 diff --git a/scripts/path_info.sh b/scripts/path_info.sh index d3f6f4e1c..d7364a7c0 100755 --- a/scripts/path_info.sh +++ b/scripts/path_info.sh @@ -40,6 +40,19 @@ declare -A supported_architectures=( ["darwin-x86_64"]="_output/bin/platforms/darwin/amd64" # Alias for darwin-amd64 ) +declare -A supported_architectures_tools=( + ["linux-amd64"]="_output/bin-tools/platforms/linux/amd64" + ["linux-arm64"]="_output/bin-tools/platforms/linux/arm64" + ["linux-mips64"]="_output/bin-tools/platforms/linux/mips64" + ["linux-mips64le"]="_output/bin-tools/platforms/linux/mips64le" + ["linux-ppc64le"]="_output/bin-tools/platforms/linux/ppc64le" + ["linux-s390x"]="_output/bin-tools/platforms/linux/s390x" + ["darwin-amd64"]="_output/bin-tools/platforms/darwin/amd64" + ["windows-amd64"]="_output/bin-tools/platforms/windows/amd64" + ["linux-x86_64"]="_output/bin-tools/platforms/linux/amd64" # Alias for linux-amd64 + ["darwin-x86_64"]="_output/bin-tools/platforms/darwin/amd64" # Alias for darwin-amd64 +) + # Check if the architecture and version are supported if [[ -z ${supported_architectures["$version-$architecture"]} ]]; then echo -e "${BLUE_PREFIX}================> Unsupported architecture: $architecture or version: $version${COLOR_SUFFIX}" @@ -50,6 +63,7 @@ echo -e "${BLUE_PREFIX}================> Architecture: $architecture${COLOR_SUFF # Set the BIN_DIR based on the architecture and version BIN_DIR=${supported_architectures["$version-$architecture"]} +BIN_DIR_TOOLS=${supported_architectures_tools["$version-$architecture"]} echo -e "${BLUE_PREFIX}================> BIN_DIR: $OPENIM_ROOT/$BIN_DIR${COLOR_SUFFIX}" @@ -84,6 +98,10 @@ config_path="$OPENIM_ROOT/config/config.yaml" configfile_path="$OPENIM_ROOT/config" log_path="$OPENIM_ROOT/log" + +component_check="component" +component_check_binary_root="$OPENIM_ROOT/$BIN_DIR_TOOLS" + # servicefile dir path service_source_root=( # api service file @@ -120,4 +138,4 @@ service_names=( "${msg_name}" "${push_name}" # "${sdk_server_name}" -) +) \ No newline at end of file diff --git a/scripts/start_all.sh b/scripts/start_all.sh index b6b4e3428..bb68546c7 100755 --- a/scripts/start_all.sh +++ b/scripts/start_all.sh @@ -77,6 +77,19 @@ need_to_start_server_shell=( start_cron.sh ) +component_check=start_component_check.sh +echo -e "" +chmod +x $component_check +echo -e "=========> ${BACKGROUND_GREEN}Executing ${component_check}...${COLOR_SUFFIX}" +echo -e "" +./$component_check +if [ $? -ne 0 ]; then + # Print error message and exit + echo -e "${BOLD_PREFIX}${RED_PREFIX}Error executing ${component_check}. Exiting...${COLOR_SUFFIX}" + exit -1 +fi + + # Loop through the script names and execute them for i in ${need_to_start_server_shell[*]}; do chmod +x $i diff --git a/scripts/start_component_check.sh b/scripts/start_component_check.sh new file mode 100644 index 000000000..6d9fa227b --- /dev/null +++ b/scripts/start_component_check.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#Include shell font styles and some basic information +SCRIPTS_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. + +#Include shell font styles and some basic information +source $SCRIPTS_ROOT/style_info.sh +source $SCRIPTS_ROOT/path_info.sh +source $SCRIPTS_ROOT/function.sh + +echo -e "${YELLOW_PREFIX}=======>SCRIPTS_ROOT=$SCRIPTS_ROOT${COLOR_SUFFIX}" +echo -e "${YELLOW_PREFIX}=======>OPENIM_ROOT=$OPENIM_ROOT${COLOR_SUFFIX}" +echo -e "${YELLOW_PREFIX}=======>pwd=$PWD${COLOR_SUFFIX}" + +bin_dir="$BIN_DIR" +logs_dir="$OPENIM_ROOT/logs" + +cd ${component_check_binary_root} +echo -e "${YELLOW_PREFIX}=======>$PWD${COLOR_SUFFIX}" +cmd="./${component_check}" +echo "==========================start components checking===========================">>$OPENIM_ROOT/logs/openIM.log +$cmd + +if [ $? -ne 0 ]; then + exit 1 +fi + + diff --git a/tools/component/go.mod b/tools/component/go.mod new file mode 100644 index 000000000..bc7abc133 --- /dev/null +++ b/tools/component/go.mod @@ -0,0 +1,3 @@ +module github.com/OpenIMSDK/Open-IM-Server/tools/component + +go 1.19 diff --git a/tools/component/main.go b/tools/component/main.go new file mode 100644 index 000000000..bc60b8140 --- /dev/null +++ b/tools/component/main.go @@ -0,0 +1,310 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "github.com/OpenIMSDK/Open-IM-Server/pkg/common/config" + "github.com/OpenIMSDK/tools/errs" + "github.com/OpenIMSDK/tools/utils" + "github.com/Shopify/sarama" + "github.com/go-zookeeper/zk" + "github.com/minio/minio-go/v7" + "github.com/redis/go-redis/v9" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" + "gopkg.in/yaml.v3" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "net" + "net/url" + "os" + "strings" + "time" + + "github.com/minio/minio-go/v7/pkg/credentials" +) + +const ( + cfgPath = "../../../../../config/config.yaml" + minioHealthCheckDuration = 1 + maxRetry = 100 + componentStartErrCode = 6000 + configErrCode = 6001 +) + +var ( + ErrComponentStart = errs.NewCodeError(componentStartErrCode, "ComponentStartErr") + ErrConfig = errs.NewCodeError(configErrCode, "Config file is incorrect") +) + +func initCfg() error { + data, err := os.ReadFile(cfgPath) + if err != nil { + return err + } + if err = yaml.Unmarshal(data, &config.Config); err != nil { + return err + } + return nil +} + +func main() { + err := initCfg() + if err != nil { + fmt.Printf("Read config failed: %v", err.Error()) + } + for i := 0; i < maxRetry; i++ { + if i != 0 { + time.Sleep(3 * time.Second) + } + fmt.Printf("Checking components Round %v......\n", i+1) + // Check MySQL + if err := checkMysql(); err != nil { + errorPrint(fmt.Sprintf("Starting Mysql failed: %v. Please make sure your mysql service has started", err.Error())) + continue + } else { + successPrint(fmt.Sprint("Mysql starts successfully")) + } + + // Check MongoDB + if err := checkMongo(); err != nil { + errorPrint(fmt.Sprintf("Starting Mongo failed: %v. Please make sure your monngo service has started", err.Error())) + continue + } else { + successPrint(fmt.Sprint("Mongo starts successfully")) + } + + // Check Minio + if err := checkMinio(); err != nil { + if index := strings.Index(err.Error(), utils.IntToString(configErrCode)); index != -1 { + successPrint(fmt.Sprint("Minio starts successfully")) + warningPrint(fmt.Sprintf("%v. Please modify your config file", err.Error())) + } else { + errorPrint(fmt.Sprintf("Starting Minio failed: %v. Please make sure your Minio service has started", err.Error())) + continue + } + } else { + successPrint(fmt.Sprint("Minio starts successfully")) + } + // Check Redis + if err := checkRedis(); err != nil { + errorPrint(fmt.Sprintf("Starting Redis failed: %v.Please make sure your Redis service has started", err.Error())) + continue + } else { + successPrint(fmt.Sprint("Redis starts successfully")) + } + + // Check Zookeeper + if err := checkZookeeper(); err != nil { + errorPrint(fmt.Sprintf("Starting Zookeeper failed: %v.Please make sure your Zookeeper service has started", err.Error())) + continue + } else { + successPrint(fmt.Sprint("Zookeeper starts successfully")) + } + + // Check Kafka + if err := checkKafka(); err != nil { + errorPrint(fmt.Sprintf("Starting Kafka failed: %v.Please make sure your Kafka service has started", err.Error())) + continue + } else { + successPrint(fmt.Sprint("Kafka starts successfully")) + } + successPrint(fmt.Sprint("All components starts successfully")) + os.Exit(0) + } + os.Exit(1) +} + +func exactIP(urll string) string { + u, _ := url.Parse(urll) + host, _, err := net.SplitHostPort(u.Host) + if err != nil { + host = u.Host + } + if strings.HasSuffix(host, ":") { + host = host[0 : len(host)-1] + } + return host +} + +func checkMysql() error { + var sqlDB *sql.DB + defer func() { + if sqlDB != nil { + sqlDB.Close() + } + }() + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local", + config.Config.Mysql.Username, config.Config.Mysql.Password, config.Config.Mysql.Address[0], "mysql") + db, err := gorm.Open(mysql.Open(dsn), nil) + if err != nil { + return err + } else { + sqlDB, err = db.DB() + err = sqlDB.Ping() + if err != nil { + return err + } + } + return nil +} + +func checkMongo() error { + var client *mongo.Client + defer func() { + if client != nil { + client.Disconnect(context.TODO()) + } + }() + mongodbHosts := "" + for i, v := range config.Config.Mongo.Address { + if i == len(config.Config.Mongo.Address)-1 { + mongodbHosts += v + } else { + mongodbHosts += v + "," + } + } + client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI( + fmt.Sprintf("mongodb://%v:%v@%v/?authSource=admin", + config.Config.Mongo.Username, config.Config.Mongo.Password, mongodbHosts))) + if err != nil { + return err + } else { + err = client.Ping(context.TODO(), &readpref.ReadPref{}) + if err != nil { + return err + } + } + return nil +} + +func checkMinio() error { + if config.Config.Object.Enable == "minio" { + conf := config.Config.Object.Minio + u, _ := url.Parse(conf.Endpoint) + minioClient, err := minio.New(u.Host, &minio.Options{ + Creds: credentials.NewStaticV4(conf.AccessKeyID, conf.SecretAccessKey, ""), + Secure: u.Scheme == "https", + }) + if err != nil { + return err + } + + cancel, err := minioClient.HealthCheck(time.Duration(minioHealthCheckDuration) * time.Second) + defer func() { + if cancel != nil { + cancel() + } + }() + if err != nil { + return err + } else { + if minioClient.IsOffline() { + return ErrComponentStart.Wrap("Minio server is offline") + } + } + if exactIP(config.Config.Object.ApiURL) == "127.0.0.1" || exactIP(config.Config.Object.Minio.Endpoint) == "127.0.0.1" { + return ErrConfig.Wrap("apiURL or Minio endpoint contain 127.0.0.1.") + } + } + return nil +} + +func checkRedis() error { + var redisClient redis.UniversalClient + defer func() { + if redisClient != nil { + redisClient.Close() + } + }() + if len(config.Config.Redis.Address) > 1 { + redisClient = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: config.Config.Redis.Address, + Username: config.Config.Redis.Username, + Password: config.Config.Redis.Password, + }) + } else { + redisClient = redis.NewClient(&redis.Options{ + Addr: config.Config.Redis.Address[0], + Username: config.Config.Redis.Username, + Password: config.Config.Redis.Password, + }) + } + _, err := redisClient.Ping(context.Background()).Result() + if err != nil { + return err + } + return nil +} + +func checkZookeeper() error { + var c *zk.Conn + defer func() { + if c != nil { + c.Close() + } + }() + c, _, err := zk.Connect(config.Config.Zookeeper.ZkAddr, time.Second) + if err != nil { + return err + } else { + if config.Config.Zookeeper.Username != "" && config.Config.Zookeeper.Password != "" { + if err := c.AddAuth("digest", []byte(config.Config.Zookeeper.Username+":"+config.Config.Zookeeper.Password)); err != nil { + return err + } + } + _, _, err = c.Get("/") + if err != nil { + return err + } + } + return nil +} + +func checkKafka() error { + var kafkaClient sarama.Client + defer func() { + if kafkaClient != nil { + kafkaClient.Close() + } + }() + cfg := sarama.NewConfig() + if config.Config.Kafka.Username != "" && config.Config.Kafka.Password != "" { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.User = config.Config.Kafka.Username + cfg.Net.SASL.Password = config.Config.Kafka.Password + } + kafkaClient, err := sarama.NewClient(config.Config.Kafka.Addr, cfg) + if err != nil { + return err + } else { + topics, err := kafkaClient.Topics() + if err != nil { + return err + } + if !utils.IsContain(config.Config.Kafka.MsgToMongo.Topic, topics) { + return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.MsgToMongo.Topic)) + } + if !utils.IsContain(config.Config.Kafka.MsgToPush.Topic, topics) { + return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.MsgToPush.Topic)) + } + if !utils.IsContain(config.Config.Kafka.LatestMsgToRedis.Topic, topics) { + return ErrComponentStart.Wrap(fmt.Sprintf("kafka doesn't contain topic:%v", config.Config.Kafka.LatestMsgToRedis.Topic)) + } + } + return nil +} + +func errorPrint(s string) { + fmt.Printf("\x1b[%dm%v\x1b[0m\n", 31, s) +} + +func successPrint(s string) { + fmt.Printf("\x1b[%dm%v\x1b[0m\n", 32, s) +} + +func warningPrint(s string) { + fmt.Printf("\x1b[%dmWarning: But %v\x1b[0m\n", 33, s) +}