mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-04-28 06:19:20 +08:00
refactor: 3.7.0 code conventions. (#2148)
* Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * feat: add code lint * feat: add code lint * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * Script Refactoring * feat: code format * Script Refactoring * Script Refactoring * Script Refactoring * Adjust MinIO configuration settings * Adjust configuration settings * Adjust configuration settings * refactor: config change. * refactor: webhooks update. * Adjust configuration settings * refactor: webhooks update. * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * feat: s3 api addr * refactor: webhooks update. * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * Adjust configuration settings * refactor: webhooks update. * refactor: kafka update. * Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service. * refactor: kafka update. * refactor: kafka update. * Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service. * Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service. * Windows can compile and run. * Windows can compile and run. * refactor: kafka update. * feat: msg cache split * refactor: webhooks update * refactor: webhooks update * refactor: friends update * refactor: group update * refactor: third update * refactor: api update * refactor: crontab update * refactor: msggateway update * mage * mage * refactor: all module update. * check * refactor: all module update. * load config * load config * load config * load config * refactor: all module update. * refactor: all module update. * refactor: all module update. * refactor: all module update. * refactor: all module update. * Optimize Docker configuration and script. * refactor: all module update. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * refactor: all module update. * Optimize Docker configuration and script. * refactor: all module update. * refactor: all module update. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * update tools * update tools * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * update protocol * Optimize Docker configuration and script. * Optimize Docker configuration and script. * refactor: all module update. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * Optimize Docker configuration and script. * refactor: api remove token auth by redis directly. * Code Refactoring * refactor: websocket auth change to call rpc of auth. * refactor: kick online user and remove token change to call auth rpc. * refactor: kick online user and remove token change to call auth rpc. * refactor: remove msggateway redis. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor webhook * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor webhook * refactor: cmd update. * refactor: cmd update. * fix: runtime: goroutine stack exceeds * refactor: cmd update. * refactor notification * refactor notification * refactor * refactor: cmd update. * refactor: cmd update. * refactor * refactor * refactor * protojson * protojson * protojson * go mod * wrapperspb * refactor: cmd update. * refactor: cmd update. * refactor: cmd update. * refactor: context update. * refactor: websocket update info. * refactor: websocket update info. * refactor: websocket update info. * refactor: websocket update info. * refactor: api name change. * refactor: debug info. * refactor: debug info. * refactor: debug info. * fix: update file * refactor * refactor * refactor: debug info. * refactor: debug info. * refactor: debug info. * refactor: debug info. * refactor: debug info. * refactor: debug info. * fix: callback update. * fix: callback update. * refactor * fix: update message. * fix: msg cache timeout. * refactor * refactor * fix: push update. * fix: push update. * fix: push update. * fix: push update. * fix: push update. * fix: push update. * fix: push update. * fix: websocket handle error remove when upgrade error. --------- Co-authored-by: skiffer-git <44203734@qq.com> Co-authored-by: Xinwei Xiong (cubxxw) <3293172751nss@gmail.com> Co-authored-by: withchao <993506633@qq.com>
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/changelog
|
||||
|
||||
go 1.19
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/discovery/zookeeper"
|
||||
"github.com/openimsdk/tools/mq/kafka"
|
||||
"github.com/openimsdk/tools/s3/minio"
|
||||
"github.com/openimsdk/tools/system/program"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxRetry = 180
|
||||
|
||||
func CheckZookeeper(ctx context.Context, config *config.ZooKeeper) error {
|
||||
// Temporary disable logging
|
||||
originalLogger := log.Default().Writer()
|
||||
log.SetOutput(ioutil.Discard)
|
||||
defer log.SetOutput(originalLogger) // Ensure logging is restored
|
||||
return zookeeper.Check(ctx, config.Address, config.Schema, zookeeper.WithUserNameAndPassword(config.Username, config.Password))
|
||||
}
|
||||
|
||||
func CheckMongo(ctx context.Context, config *config.Mongo) error {
|
||||
return mongoutil.Check(ctx, config.Build())
|
||||
}
|
||||
|
||||
func CheckRedis(ctx context.Context, config *config.Redis) error {
|
||||
return redisutil.Check(ctx, config.Build())
|
||||
}
|
||||
|
||||
func CheckMinIO(ctx context.Context, config *config.Minio) error {
|
||||
return minio.Check(ctx, config.Build())
|
||||
}
|
||||
|
||||
func CheckKafka(ctx context.Context, conf *config.Kafka) error {
|
||||
return kafka.Check(ctx, conf.Build(), []string{conf.ToMongoTopic, conf.ToRedisTopic, conf.ToPushTopic})
|
||||
}
|
||||
|
||||
func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.ZooKeeper, error) {
|
||||
var (
|
||||
mongoConfig = &config.Mongo{}
|
||||
redisConfig = &config.Redis{}
|
||||
kafkaConfig = &config.Kafka{}
|
||||
minioConfig = &config.Minio{}
|
||||
zookeeperConfig = &config.ZooKeeper{}
|
||||
)
|
||||
err := config.LoadConfig(filepath.Join(configDir, cmd.MongodbConfigFileName), cmd.ConfigEnvPrefixMap[cmd.MongodbConfigFileName], mongoConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = config.LoadConfig(filepath.Join(configDir, cmd.RedisConfigFileName), cmd.ConfigEnvPrefixMap[cmd.RedisConfigFileName], redisConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = config.LoadConfig(filepath.Join(configDir, cmd.KafkaConfigFileName), cmd.ConfigEnvPrefixMap[cmd.KafkaConfigFileName], kafkaConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = config.LoadConfig(filepath.Join(configDir, cmd.MinioConfigFileName), cmd.ConfigEnvPrefixMap[cmd.MinioConfigFileName], minioConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = config.LoadConfig(filepath.Join(configDir, cmd.ZookeeperConfigFileName), cmd.ConfigEnvPrefixMap[cmd.ZookeeperConfigFileName], zookeeperConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
return mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var index int
|
||||
var configDir string
|
||||
flag.IntVar(&index, "i", 0, "Index number")
|
||||
defaultConfigDir := filepath.Join("..", "..", "..", "..", "..", "config")
|
||||
flag.StringVar(&configDir, "c", defaultConfigDir, "Configuration dir")
|
||||
flag.Parse()
|
||||
|
||||
fmt.Printf("%s Index: %d, Config Path: %s\n", filepath.Base(os.Args[0]), index, configDir)
|
||||
|
||||
mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, err := initConfig(configDir)
|
||||
if err != nil {
|
||||
program.ExitWithError(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = performChecks(ctx, mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, maxRetry)
|
||||
if err != nil {
|
||||
// Assume program.ExitWithError logs the error and exits.
|
||||
// Replace with your error handling logic as necessary.
|
||||
program.ExitWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func performChecks(ctx context.Context, mongoConfig *config.Mongo, redisConfig *config.Redis, kafkaConfig *config.Kafka, minioConfig *config.Minio, zookeeperConfig *config.ZooKeeper, maxRetry int) error {
|
||||
checksDone := make(map[string]bool)
|
||||
|
||||
checks := map[string]func() error{
|
||||
"Zookeeper": func() error {
|
||||
return CheckZookeeper(ctx, zookeeperConfig)
|
||||
},
|
||||
"Mongo": func() error {
|
||||
return CheckMongo(ctx, mongoConfig)
|
||||
},
|
||||
"Redis": func() error {
|
||||
return CheckRedis(ctx, redisConfig)
|
||||
},
|
||||
"MinIO": func() error {
|
||||
return CheckMinIO(ctx, minioConfig)
|
||||
},
|
||||
"Kafka": func() error {
|
||||
return CheckKafka(ctx, kafkaConfig)
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
allSuccess := true
|
||||
for name, check := range checks {
|
||||
if !checksDone[name] {
|
||||
if err := check(); err != nil {
|
||||
fmt.Printf("%s check failed: %v\n", name, err)
|
||||
allSuccess = false
|
||||
} else {
|
||||
fmt.Printf("%s check succeeded.\n", name)
|
||||
checksDone[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allSuccess {
|
||||
fmt.Println("All components checks passed successfully.")
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return fmt.Errorf("not all components checks passed successfully after %d attempts", maxRetry)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
func main() {
|
||||
vMem, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get virtual memory info: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Use the Available field to get the available memory
|
||||
availableMemoryGB := float64(vMem.Available) / float64(1024*1024*1024)
|
||||
|
||||
if availableMemoryGB < 1.0 {
|
||||
fmt.Fprintf(os.Stderr, "System available memory is less than 1GB: %.2fGB\n", availableMemoryGB)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Printf("System available memory is sufficient: %.2fGB\n", availableMemoryGB)
|
||||
}
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/kafka"
|
||||
|
||||
"github.com/OpenIMSDK/tools/component"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultCfgPath is the default path of the configuration file.
|
||||
defaultCfgPath = "../../../../../config/config.yaml"
|
||||
maxRetry = 100
|
||||
)
|
||||
|
||||
var (
|
||||
cfgPath = flag.String("c", defaultCfgPath, "Path to the configuration file")
|
||||
)
|
||||
|
||||
func initCfg() (*config.GlobalConfig, error) {
|
||||
data, err := os.ReadFile(*cfgPath)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, "ReadFile unmarshal failed")
|
||||
}
|
||||
|
||||
conf := config.NewGlobalConfig()
|
||||
err = yaml.Unmarshal(data, &conf)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, "InitConfig unmarshal failed")
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
type checkFunc struct {
|
||||
name string
|
||||
function func(*config.GlobalConfig) error
|
||||
flag bool
|
||||
config *config.GlobalConfig
|
||||
}
|
||||
|
||||
// colorErrPrint prints formatted string in red to stderr
|
||||
func colorErrPrint(msg string) {
|
||||
// ANSI escape code for red text
|
||||
const redColor = "\033[31m"
|
||||
// ANSI escape code to reset color
|
||||
const resetColor = "\033[0m"
|
||||
msg = redColor + msg + resetColor
|
||||
// Print to stderr in red
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
}
|
||||
|
||||
func colorSuccessPrint(format string, a ...interface{}) {
|
||||
// ANSI escape code for green text is \033[32m
|
||||
// \033[0m resets the color
|
||||
fmt.Printf("\033[32m"+format+"\033[0m", a...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
conf, err := initCfg()
|
||||
if err != nil {
|
||||
fmt.Printf("Read config failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = configGetEnv(conf)
|
||||
if err != nil {
|
||||
fmt.Printf("configGetEnv failed, err:%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
checks := []checkFunc{
|
||||
{name: "Mongo", function: checkMongo, config: conf},
|
||||
{name: "Redis", function: checkRedis, config: conf},
|
||||
{name: "Zookeeper", function: checkZookeeper, config: conf},
|
||||
{name: "Kafka", function: checkKafka, config: conf},
|
||||
}
|
||||
if conf.Object.Enable == "minio" {
|
||||
checks = append(checks, checkFunc{name: "Minio", function: checkMinio, config: conf})
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
if i != 0 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
fmt.Printf("Checking components round %v...\n", i+1)
|
||||
|
||||
var err error
|
||||
allSuccess := true
|
||||
for index, check := range checks {
|
||||
if !check.flag {
|
||||
err = check.function(check.config)
|
||||
if err != nil {
|
||||
allSuccess = false
|
||||
colorErrPrint(fmt.Sprintf("Check component: %s, failed: %v", check.name, err.Error()))
|
||||
|
||||
if check.name == "Minio" {
|
||||
if errors.Is(err, errMinioNotEnabled) ||
|
||||
errors.Is(err, errSignEndPoint) ||
|
||||
errors.Is(err, errApiURL) {
|
||||
checks[index].flag = true
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
checks[index].flag = true
|
||||
component.SuccessPrint(fmt.Sprintf("%s connected successfully", check.name))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if allSuccess {
|
||||
component.SuccessPrint("All components started successfully!")
|
||||
return
|
||||
}
|
||||
}
|
||||
component.ErrorPrint("Some components checked failed!")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
var errMinioNotEnabled = errors.New("minio.Enable is not configured to use MinIO")
|
||||
|
||||
var errSignEndPoint = errors.New("minio.signEndPoint contains 127.0.0.1, causing issues with image sending")
|
||||
var errApiURL = errors.New("object.apiURL contains 127.0.0.1, causing issues with image sending")
|
||||
|
||||
// checkMongo checks the MongoDB connection without retries
|
||||
func checkMongo(config *config.GlobalConfig) error {
|
||||
mongoStu := &component.Mongo{
|
||||
URL: config.Mongo.Uri,
|
||||
Address: config.Mongo.Address,
|
||||
Database: config.Mongo.Database,
|
||||
Username: config.Mongo.Username,
|
||||
Password: config.Mongo.Password,
|
||||
MaxPoolSize: config.Mongo.MaxPoolSize,
|
||||
}
|
||||
err := component.CheckMongo(mongoStu)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// checkRedis checks the Redis connection
|
||||
func checkRedis(config *config.GlobalConfig) error {
|
||||
redisStu := &component.Redis{
|
||||
Address: config.Redis.Address,
|
||||
Username: config.Redis.Username,
|
||||
Password: config.Redis.Password,
|
||||
}
|
||||
err := component.CheckRedis(redisStu)
|
||||
return err
|
||||
}
|
||||
|
||||
// checkMinio checks the MinIO connection
|
||||
func checkMinio(config *config.GlobalConfig) error {
|
||||
if strings.Contains(config.Object.ApiURL, "127.0.0.1") {
|
||||
return errs.Wrap(errApiURL)
|
||||
}
|
||||
if config.Object.Enable != "minio" {
|
||||
return errs.Wrap(errMinioNotEnabled)
|
||||
}
|
||||
if strings.Contains(config.Object.Minio.Endpoint, "127.0.0.1") {
|
||||
return errs.Wrap(errSignEndPoint)
|
||||
}
|
||||
|
||||
minio := &component.Minio{
|
||||
ApiURL: config.Object.ApiURL,
|
||||
Endpoint: config.Object.Minio.Endpoint,
|
||||
AccessKeyID: config.Object.Minio.AccessKeyID,
|
||||
SecretAccessKey: config.Object.Minio.SecretAccessKey,
|
||||
SignEndpoint: config.Object.Minio.SignEndpoint,
|
||||
UseSSL: getEnv("MINIO_USE_SSL", "false"),
|
||||
}
|
||||
err := component.CheckMinio(minio)
|
||||
return err
|
||||
}
|
||||
|
||||
// checkZookeeper checks the Zookeeper connection
|
||||
func checkZookeeper(config *config.GlobalConfig) error {
|
||||
zkStu := &component.Zookeeper{
|
||||
Schema: config.Zookeeper.Schema,
|
||||
ZkAddr: config.Zookeeper.ZkAddr,
|
||||
Username: config.Zookeeper.Username,
|
||||
Password: config.Zookeeper.Password,
|
||||
}
|
||||
err := component.CheckZookeeper(zkStu)
|
||||
return err
|
||||
}
|
||||
|
||||
// checkKafka checks the Kafka connection
|
||||
func checkKafka(config *config.GlobalConfig) error {
|
||||
// Prioritize environment variables
|
||||
kafkaStu := &component.Kafka{
|
||||
Username: config.Kafka.Username,
|
||||
Password: config.Kafka.Password,
|
||||
Addr: config.Kafka.Addr,
|
||||
}
|
||||
|
||||
kafkaClient, err := component.CheckKafka(kafkaStu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer kafkaClient.Close()
|
||||
|
||||
// Verify if necessary topics exist
|
||||
topics, err := kafkaClient.Topics()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
requiredTopics := []string{
|
||||
config.Kafka.MsgToMongo.Topic,
|
||||
config.Kafka.MsgToPush.Topic,
|
||||
config.Kafka.LatestMsgToRedis.Topic,
|
||||
}
|
||||
|
||||
for _, requiredTopic := range requiredTopics {
|
||||
if !isTopicPresent(requiredTopic, topics) {
|
||||
return errs.Wrap(err, fmt.Sprintf("Kafka doesn't contain topic: %v", requiredTopic))
|
||||
}
|
||||
}
|
||||
|
||||
var tlsConfig *kafka.TLSConfig
|
||||
if config.Kafka.TLS != nil {
|
||||
tlsConfig = &kafka.TLSConfig{
|
||||
CACrt: config.Kafka.TLS.CACrt,
|
||||
ClientCrt: config.Kafka.TLS.ClientCrt,
|
||||
ClientKey: config.Kafka.TLS.ClientKey,
|
||||
ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd,
|
||||
InsecureSkipVerify: config.Kafka.TLS.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
_, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{
|
||||
KafkaVersion: sarama.V2_0_0_0,
|
||||
OffsetsInitial: sarama.OffsetNewest,
|
||||
IsReturnErr: false,
|
||||
UserName: config.Kafka.Username,
|
||||
Password: config.Kafka.Password,
|
||||
}, []string{config.Kafka.LatestMsgToRedis.Topic},
|
||||
config.Kafka.Addr, config.Kafka.ConsumerGroupID.MsgToRedis, tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{
|
||||
KafkaVersion: sarama.V2_0_0_0,
|
||||
OffsetsInitial: sarama.OffsetNewest, IsReturnErr: false,
|
||||
}, []string{config.Kafka.MsgToMongo.Topic},
|
||||
config.Kafka.Addr, config.Kafka.ConsumerGroupID.MsgToMongo, tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{
|
||||
KafkaVersion: sarama.V2_0_0_0,
|
||||
OffsetsInitial: sarama.OffsetNewest, IsReturnErr: false,
|
||||
}, []string{config.Kafka.MsgToPush.Topic}, config.Kafka.Addr,
|
||||
config.Kafka.ConsumerGroupID.MsgToPush, tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isTopicPresent checks if a topic is present in the list of topics
|
||||
func isTopicPresent(topic string, topics []string) bool {
|
||||
for _, t := range topics {
|
||||
if t == topic {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func configGetEnv(config *config.GlobalConfig) error {
|
||||
config.Mongo.Uri = getEnv("MONGO_URI", config.Mongo.Uri)
|
||||
config.Mongo.Username = getEnv("MONGO_OPENIM_USERNAME", config.Mongo.Username)
|
||||
config.Mongo.Password = getEnv("MONGO_OPENIM_PASSWORD", config.Mongo.Password)
|
||||
config.Mongo.Address = getArrEnv("MONGO_ADDRESS", "MONGO_PORT", config.Mongo.Address)
|
||||
config.Mongo.Database = getEnv("MONGO_DATABASE", config.Mongo.Database)
|
||||
maxPoolSize, err := getEnvInt("MONGO_MAX_POOL_SIZE", config.Mongo.MaxPoolSize)
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "MONGO_MAX_POOL_SIZE")
|
||||
}
|
||||
config.Mongo.MaxPoolSize = maxPoolSize
|
||||
|
||||
config.Redis.Username = getEnv("REDIS_USERNAME", config.Redis.Username)
|
||||
config.Redis.Password = getEnv("REDIS_PASSWORD", config.Redis.Password)
|
||||
config.Redis.Address = getArrEnv("REDIS_ADDRESS", "REDIS_PORT", config.Redis.Address)
|
||||
|
||||
config.Object.ApiURL = getEnv("OBJECT_APIURL", config.Object.ApiURL)
|
||||
config.Object.Minio.Endpoint = getEnv("MINIO_ENDPOINT", config.Object.Minio.Endpoint)
|
||||
config.Object.Minio.AccessKeyID = getEnv("MINIO_ACCESS_KEY_ID", config.Object.Minio.AccessKeyID)
|
||||
config.Object.Minio.SecretAccessKey = getEnv("MINIO_SECRET_ACCESS_KEY", config.Object.Minio.SecretAccessKey)
|
||||
config.Object.Minio.SignEndpoint = getEnv("MINIO_SIGN_ENDPOINT", config.Object.Minio.SignEndpoint)
|
||||
|
||||
config.Zookeeper.Schema = getEnv("ZOOKEEPER_SCHEMA", config.Zookeeper.Schema)
|
||||
config.Zookeeper.ZkAddr = getArrEnv("ZOOKEEPER_ADDRESS", "ZOOKEEPER_PORT", config.Zookeeper.ZkAddr)
|
||||
config.Zookeeper.Username = getEnv("ZOOKEEPER_USERNAME", config.Zookeeper.Username)
|
||||
config.Zookeeper.Password = getEnv("ZOOKEEPER_PASSWORD", config.Zookeeper.Password)
|
||||
|
||||
config.Kafka.Username = getEnv("KAFKA_USERNAME", config.Kafka.Username)
|
||||
config.Kafka.Password = getEnv("KAFKA_PASSWORD", config.Kafka.Password)
|
||||
config.Kafka.Addr = getArrEnv("KAFKA_ADDRESS", "KAFKA_PORT", config.Kafka.Addr)
|
||||
config.Object.Minio.Endpoint = getMinioAddr("MINIO_ENDPOINT", "MINIO_ADDRESS", "MINIO_PORT", config.Object.Minio.Endpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMinioAddr(key1, key2, key3, fallback string) string {
|
||||
// Prioritize environment variables
|
||||
endpoint := getEnv(key1, fallback)
|
||||
address, addressExist := os.LookupEnv(key2)
|
||||
port, portExist := os.LookupEnv(key3)
|
||||
if portExist && addressExist {
|
||||
endpoint = "http://" + address + ":" + port
|
||||
return endpoint
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
// Helper function to get environment variable or default value
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// Helper function to get environment variable or default value
|
||||
func getEnvInt(key string, fallback int) (int, error) {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
val, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err, "string to int failed")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
return fallback, nil
|
||||
}
|
||||
|
||||
func getArrEnv(key1, key2 string, fallback []string) []string {
|
||||
address, addrExists := os.LookupEnv(key1)
|
||||
port, portExists := os.LookupEnv(key2)
|
||||
|
||||
if addrExists && portExists {
|
||||
addresses := strings.Split(address, ",")
|
||||
for i, addr := range addresses {
|
||||
addresses[i] = addr + ":" + port
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
if addrExists && !portExists {
|
||||
addresses := strings.Split(address, ",")
|
||||
for i, addr := range addresses {
|
||||
addresses[i] = addr + ":" + "0"
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
if !addrExists && portExists {
|
||||
result := make([]string, len(fallback))
|
||||
for i, addr := range fallback {
|
||||
add := strings.Split(addr, ":")
|
||||
result[i] = add[0] + ":" + port
|
||||
}
|
||||
return result
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func TestRedis(t *testing.T) {
|
||||
conf, err := initCfg()
|
||||
conf.Redis.Address = []string{
|
||||
"172.16.8.142:7000",
|
||||
//"172.16.8.142:7000", "172.16.8.142:7001", "172.16.8.142:7002", "172.16.8.142:7003", "172.16.8.142:7004", "172.16.8.142:7005",
|
||||
}
|
||||
|
||||
var redisClient redis.UniversalClient
|
||||
defer func() {
|
||||
if redisClient != nil {
|
||||
redisClient.Close()
|
||||
}
|
||||
}()
|
||||
if len(conf.Redis.Address) > 1 {
|
||||
redisClient = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: conf.Redis.Address,
|
||||
Username: conf.Redis.Username,
|
||||
Password: conf.Redis.Password,
|
||||
})
|
||||
} else {
|
||||
redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: conf.Redis.Address[0],
|
||||
Username: conf.Redis.Username,
|
||||
Password: conf.Redis.Password,
|
||||
})
|
||||
}
|
||||
_, err = redisClient.Ping(context.Background()).Result()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
val, err := redisClient.Set(context.Background(), "b_"+strconv.Itoa(i), "test", time.Second*10).Result()
|
||||
t.Log("index", i, "resp", val, "err", err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/component
|
||||
|
||||
go 1.19
|
||||
@@ -1,18 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/imctl
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/mitchellh/go-wordwrap v1.0.1
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/russross/blackfriday v1.6.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,11 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/infra
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/fatih/color v1.15.0
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -25,13 +25,14 @@ func printLinks() {
|
||||
blue := color.New(color.FgBlue).SprintFunc()
|
||||
fmt.Printf("OpenIM Github: %s\n", blue("https://github.com/OpenIMSDK/Open-IM-Server"))
|
||||
fmt.Printf("Slack Invitation: %s\n", blue("https://openimsdk.slack.com"))
|
||||
fmt.Printf("Follow Twitter: %s\n", blue("https://twitter.com/founder_im63606"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
yellow := color.New(color.FgYellow)
|
||||
blue := color.New(color.FgBlue, color.Bold)
|
||||
|
||||
yellow.Println("Current module is still under development.")
|
||||
yellow.Println("Please use the release branch or tag for production environments!")
|
||||
|
||||
message := `
|
||||
____ _____ __ __
|
||||
@@ -1,5 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/ncpu
|
||||
|
||||
go 1.19
|
||||
|
||||
require go.uber.org/automaxprocs v1.5.3
|
||||
@@ -1,7 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# 使用官方Go镜像作为基础镜像
|
||||
FROM golang:1.21 AS build-env
|
||||
ENV CGO_ENABLED=0
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 安装curl和unzip工具
|
||||
#RUN apt-get update && apt-get install -y curl unzip
|
||||
|
||||
# 从GitHub下载并解压dist.zip
|
||||
#RUN curl -LO https://github.com/OpenIMSDK/dist.zip \
|
||||
# && unzip dist.zip -d ./ \
|
||||
# && rm dist.zip
|
||||
|
||||
# 复制Go代码到容器
|
||||
COPY . .
|
||||
|
||||
# 编译Go代码
|
||||
RUN go build -o openim-web
|
||||
|
||||
# 使用轻量级的基础镜像
|
||||
FROM debian:buster-slim
|
||||
|
||||
# 将编译好的二进制文件和dist资源复制到新的容器
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/openim-web /app/openim-web
|
||||
COPY --from=build-env /app/dist /app/dist
|
||||
|
||||
# 开放容器的20001端口
|
||||
EXPOSE 20001
|
||||
|
||||
# 指定容器启动命令
|
||||
ENTRYPOINT ["/app/openim-web"]
|
||||
@@ -1,78 +0,0 @@
|
||||
# OpenIM Web Service
|
||||
|
||||
- [OpenIM Web Service](#openim-web-service)
|
||||
- [Overview](#overview)
|
||||
- [User](#user)
|
||||
- [Docker Deployment](#docker-deployment)
|
||||
- [Build the Docker Image](#build-the-docker-image)
|
||||
- [Run the Docker Container](#run-the-docker-container)
|
||||
- [Configuration](#configuration)
|
||||
- [Contributions](#contributions)
|
||||
|
||||
|
||||
OpenIM Web Service is a lightweight containerized service built with Go. The service serves static files and allows customization via environment variables.
|
||||
|
||||
## Overview
|
||||
|
||||
- Built using Go.
|
||||
- Deployed as a Docker container.
|
||||
- Serves static files from a directory which can be set via an environment variable.
|
||||
- The default port for the service is `20001`, but it can be customized using an environment variable.
|
||||
|
||||
## User
|
||||
|
||||
example:
|
||||
|
||||
```bash
|
||||
$ ./openim-web -h
|
||||
Usage of ./openim-web:
|
||||
-distPath string
|
||||
Path to the distribution (default "/app/dist")
|
||||
-port string
|
||||
Port to run the server on (default "20001")
|
||||
```
|
||||
|
||||
Variables can be set as above, Environment variables can also be set
|
||||
|
||||
example:
|
||||
|
||||
```bash
|
||||
$ export OPENIM_WEB_PPRT="11001"
|
||||
```
|
||||
|
||||
Initialize the env configuration file:
|
||||
|
||||
```bash
|
||||
$ make init
|
||||
```
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
### Build the Docker Image
|
||||
|
||||
Even though we've implemented automation, it's to make the developer experience easier:
|
||||
|
||||
To build the Docker image for OpenIM Web Service:
|
||||
|
||||
```bash
|
||||
$ docker build -t openim-web .
|
||||
```
|
||||
|
||||
### Run the Docker Container
|
||||
|
||||
To run the service:
|
||||
|
||||
```bash
|
||||
$ docker run -e DIST_PATH=/app/dist -e PORT=20001 -p 20001:20001 openim-web
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure the OpenIM Web Service using the following environment variables:
|
||||
|
||||
- **DIST_PATH**: The path to the directory containing the static files. Default: `/app/dist`.
|
||||
- **PORT**: The port on which the service listens. Default: `11001`.
|
||||
|
||||
## Contributions
|
||||
|
||||
We welcome contributions from the community. If you find any bugs or have feature suggestions, please create an issue or send a pull request.
|
||||
@@ -1,7 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/openim-web
|
||||
|
||||
go 1.19
|
||||
|
||||
require gopkg.in/yaml.v2 v2.4.0
|
||||
|
||||
require github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
@@ -1,10 +0,0 @@
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
@@ -1,63 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/NYTimes/gziphandler"
|
||||
)
|
||||
|
||||
var (
|
||||
distPathFlag string
|
||||
portFlag string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&distPathFlag, "distPath", "/app/dist", "Path to the distribution")
|
||||
flag.StringVar(&portFlag, "port", "11001", "Port to run the server on")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
distPath := getConfigValue("DIST_PATH", distPathFlag, "/app/dist")
|
||||
fs := http.FileServer(http.Dir(distPath))
|
||||
|
||||
withGzip := gziphandler.GzipHandler(fs)
|
||||
|
||||
http.Handle("/", withGzip)
|
||||
|
||||
port := getConfigValue("PORT", portFlag, "11001")
|
||||
log.Printf("Server listening on port %s in %s...", port, distPath)
|
||||
err := http.ListenAndServe(":"+port, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getConfigValue(envKey, flagValue, fallback string) string {
|
||||
envVal := os.Getenv(envKey)
|
||||
if envVal != "" {
|
||||
return envVal
|
||||
}
|
||||
if flagValue != "" {
|
||||
return flagValue
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetConfigValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envKey string
|
||||
envValue string
|
||||
flagValue string
|
||||
fallback string
|
||||
wantResult string
|
||||
}{
|
||||
{
|
||||
name: "environment variable set",
|
||||
envKey: "TEST_KEY",
|
||||
envValue: "envValue",
|
||||
flagValue: "",
|
||||
fallback: "default",
|
||||
wantResult: "envValue",
|
||||
},
|
||||
{
|
||||
name: "flag set and environment variable not set",
|
||||
envKey: "TEST_KEY",
|
||||
envValue: "",
|
||||
flagValue: "flagValue",
|
||||
fallback: "default",
|
||||
wantResult: "flagValue",
|
||||
},
|
||||
{
|
||||
name: "nothing set, use fallback",
|
||||
envKey: "TEST_KEY",
|
||||
envValue: "",
|
||||
flagValue: "",
|
||||
fallback: "default",
|
||||
wantResult: "default",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.envValue != "" {
|
||||
os.Setenv(tt.envKey, tt.envValue)
|
||||
defer os.Unsetenv(tt.envKey)
|
||||
}
|
||||
|
||||
got := getConfigValue(tt.envKey, tt.flagValue, tt.fallback)
|
||||
|
||||
if got != tt.wantResult {
|
||||
t.Errorf("getConfigValue(%s, %s, %s) = %s; want %s", tt.envKey, tt.flagValue, tt.fallback, got, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/url2im
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/OpenIMSDK/protocol v0.0.21
|
||||
github.com/kelindar/bitmap v1.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/kelindar/simd v1.1.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
@@ -1,33 +0,0 @@
|
||||
github.com/OpenIMSDK/protocol v0.0.21 h1:5H6H+hJ9d/VgRqttvxD/zfK9Asd+4M8Eknk5swSbUVY=
|
||||
github.com/OpenIMSDK/protocol v0.0.21/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/kelindar/bitmap v1.5.1 h1:+ZmZdwHbJ+CGE+q/aAJ74KJSnp0vOlGD7KY5x51mVzk=
|
||||
github.com/kelindar/bitmap v1.5.1/go.mod h1:j3qZjxH9s4OtvsnFTP2bmPkjqil9Y2xQlxPYHexasEA=
|
||||
github.com/kelindar/simd v1.1.2 h1:KduKb+M9cMY2HIH8S/cdJyD+5n5EGgq+Aeeleos55To=
|
||||
github.com/kelindar/simd v1.1.2/go.mod h1:inq4DFudC7W8L5fhxoeZflLRNpWSs0GNx6MlWFvuvr0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/auth"
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
"github.com/OpenIMSDK/protocol/third"
|
||||
"github.com/openimsdk/protocol/auth"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/third"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
@@ -39,7 +39,7 @@ type Api struct {
|
||||
func (a *Api) apiPost(ctx context.Context, path string, req any, resp any) error {
|
||||
operationID, _ := ctx.Value("operationID").(string)
|
||||
if operationID == "" {
|
||||
return errors.New("call api operationID is empty")
|
||||
return errs.New("call api operationID is empty")
|
||||
}
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/third"
|
||||
"github.com/openimsdk/protocol/third"
|
||||
)
|
||||
|
||||
type Upload struct {
|
||||
@@ -256,10 +256,10 @@ func (m *Manage) RunTask(ctx context.Context, task Task) (string, error) {
|
||||
|
||||
func (m *Manage) partSize(size int64) (int64, error) {
|
||||
if size <= 0 {
|
||||
return 0, errors.New("size must be greater than 0")
|
||||
return 0, errs.New("size must be greater than 0")
|
||||
}
|
||||
if size > m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize) {
|
||||
return 0, fmt.Errorf("size must be less than %db", m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize))
|
||||
return 0, errs.New("size must be less than", "size", m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize))
|
||||
}
|
||||
if size <= m.partLimit.MinPartSize*int64(m.partLimit.MaxNumSize) {
|
||||
return m.partLimit.MinPartSize, nil
|
||||
@@ -392,7 +392,7 @@ func (m *Manage) HttpGet(ctx context.Context, url string) (*http.Response, error
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return nil, fmt.Errorf("http get %s status %s", url, response.Status)
|
||||
return nil, fmt.Errorf("webhook get %s status %s", url, response.Status)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/versionchecker
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/fatih/color v1.15.0
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -19,10 +19,9 @@ import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
//"github.com/openimsdk/open-im-server/v3/pkg/common/version"
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
)
|
||||
|
||||
func ExecuteCommand(cmdName string, args ...string) (string, error) {
|
||||
@@ -40,8 +39,7 @@ func ExecuteCommand(cmdName string, args ...string) (string, error) {
|
||||
}
|
||||
|
||||
func printTime() string {
|
||||
currentTime := time.Now()
|
||||
formattedTime := currentTime.Format("2006-01-02 15:04:05")
|
||||
formattedTime := timeutil.GetCurrentTimeFormatted()
|
||||
return fmt.Sprintf("Current Date & Time: %s", formattedTime)
|
||||
}
|
||||
|
||||
@@ -60,14 +58,6 @@ func getDockerVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
func getDockerComposeVersion() string {
|
||||
version, err := ExecuteCommand("docker-compose", "--version")
|
||||
if err != nil {
|
||||
return "Docker Compose is not installed. Please install it to get the version."
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func getKubernetesVersion() string {
|
||||
version, err := ExecuteCommand("kubectl", "version", "--client", "--short")
|
||||
if err != nil {
|
||||
@@ -101,20 +91,15 @@ func getGitVersion() string {
|
||||
|
||||
func main() {
|
||||
// red := color.New(color.FgRed).SprintFunc()
|
||||
green := color.New(color.FgGreen).SprintFunc()
|
||||
// green := color.New(color.FgGreen).SprintFunc()
|
||||
blue := color.New(color.FgBlue).SprintFunc()
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
|
||||
fmt.Println(green(printTime()))
|
||||
fmt.Println(yellow("# Diagnostic Tool Result\n"))
|
||||
// yellow := color.New(color.FgYellow).SprintFunc()
|
||||
fmt.Println(blue("## Go Version"))
|
||||
fmt.Println(getGoVersion())
|
||||
fmt.Println(blue("## Branch Type"))
|
||||
fmt.Println(getGitVersion())
|
||||
fmt.Println(blue("## Docker Version"))
|
||||
fmt.Println(getDockerVersion())
|
||||
fmt.Println(blue("## Docker Compose Version"))
|
||||
fmt.Println(getDockerComposeVersion())
|
||||
fmt.Println(blue("## Kubernetes Version"))
|
||||
fmt.Println(getKubernetesVersion())
|
||||
// fmt.Println(blue("## OpenIM Versions"))
|
||||
@@ -1,8 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/v3/tools/yamlfmt
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/likexian/gokit v0.25.13
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
|
||||
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user