Compare commits

..

11 Commits

Author SHA1 Message Date
刘祥超
e4e0aab010 阶段性提交 2022-09-28 17:38:52 +08:00
刘祥超
ed87b4e2a9 用户节点版本改为0.5.4 2022-09-28 08:56:40 +08:00
刘祥超
337eb36d25 DNS版本改为0.2.8 2022-09-28 08:16:57 +08:00
刘祥超
c44e40d72d systemd服务增加BEGIN INIT INFO 2022-09-28 08:16:49 +08:00
刘祥超
2e8ba831a1 DNS版本修改为0.2.7.1 2022-09-27 08:06:16 +08:00
刘祥超
a706c2a5a5 将版本修改为0.5.4 2022-09-26 15:17:00 +08:00
刘祥超
093826222a 将版本修改为v0.5.3.1 2022-09-26 12:16:44 +08:00
刘祥超
0d7b487afc 修复开源版本无法编译的问题 2022-09-26 12:16:20 +08:00
刘祥超
8de17b6d9c 只有在数据库用户是root时才执行某些命令 2022-09-26 12:16:08 +08:00
刘祥超
49d217a883 提交EdgeDNS API相关代码 2022-09-26 11:51:45 +08:00
刘祥超
4827555899 启动过程增加多个提示 2022-09-26 11:00:58 +08:00
23 changed files with 239 additions and 394 deletions

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.5.3"
Version = "0.5.4"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -18,9 +18,9 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.5.3"
NodeVersion = "0.5.4"
UserNodeVersion = "0.5.0"
DNSNodeVersion = "0.2.7"
DNSNodeVersion = "0.2.8"
AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.4"
ReportNodeVersion = "0.1.1"

View File

@@ -13,22 +13,24 @@ type OrderMethod struct {
Url string `field:"url"` // URL
Secret string `field:"secret"` // 密钥
Params dbs.JSON `field:"params"` // 参数
ClientType string `field:"clientType"` // 客户端类型
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type OrderMethodOperator struct {
Id interface{} // ID
Name interface{} // 名称
IsOn interface{} // 是否启用
Description interface{} // 描述
ParentCode interface{} // 内置的父级代号
Code interface{} // 代号
Url interface{} // URL
Secret interface{} // 密钥
Params interface{} // 参数
Order interface{} // 排序
State interface{} // 状态
Id any // ID
Name any // 名称
IsOn any // 是否启用
Description any // 描述
ParentCode any // 内置的父级代号
Code any // 代号
Url any // URL
Secret any // 密钥
Params any // 参数
ClientType any // 客户端类型
Order any // 排序
State any // 状态
}
func NewOrderMethodOperator() *OrderMethodOperator {

View File

@@ -3,7 +3,6 @@ package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
)
@@ -29,47 +28,3 @@ func (this *NSCluster) HasDDoSProtection() bool {
}
return false
}
// DecodeHosts 解析主机地址
func (this *NSCluster) DecodeHosts() []string {
if IsNull(this.Hosts) {
return nil
}
var hosts = []string{}
err := json.Unmarshal(this.Hosts, &hosts)
if err != nil {
remotelogs.Error("NSCluster.DecodeHosts", "decode failed: "+err.Error())
}
return hosts
}
// DecodeSOAConfig 解析SOA设置
func (this *NSCluster) DecodeSOAConfig() *dnsconfigs.NSSOAConfig {
var config = dnsconfigs.DefaultNSSOAConfig()
if IsNull(this.Soa) {
return config
}
err := json.Unmarshal(this.Soa, config)
if err != nil {
remotelogs.Error("NSCluster.DecodeSOAConfig", "decode failed: "+err.Error())
}
return config
}
// DecodeAnswerConfig 解析应答设置
func (this *NSCluster) DecodeAnswerConfig() *dnsconfigs.NSAnswerConfig {
var config = dnsconfigs.DefaultNSAnswerConfig()
if IsNull(this.Answer) {
return config
}
err := json.Unmarshal(this.Answer, config)
if err != nil {
remotelogs.Error("NSCluster.DecodeAnswerConfig", "decode failed: "+err.Error())
}
return config
}

View File

@@ -300,7 +300,7 @@ func (this *UserNodeDAO) CountAllEnabledUserNodesWithSSLPolicyIds(tx *dbs.Tx, ss
if len(sslPolicyIds) == 0 {
return
}
policyStringIds := []string{}
var policyStringIds = []string{}
for _, policyId := range sslPolicyIds {
policyStringIds = append(policyStringIds, strconv.FormatInt(policyId, 10))
}
@@ -310,3 +310,21 @@ func (this *UserNodeDAO) CountAllEnabledUserNodesWithSSLPolicyIds(tx *dbs.Tx, ss
Param("policyIds", strings.Join(policyStringIds, ",")).
Count()
}
// FindUserNodeAccessAddr 获取用户节点访问地址
func (this *UserNodeDAO) FindUserNodeAccessAddr(tx *dbs.Tx) (string, error) {
nodes, err := this.ListEnabledUserNodes(tx, 0, 100)
if err != nil {
return "", err
}
for _, node := range nodes {
addrs, err := node.DecodeAccessAddrStrings()
if err != nil {
continue
}
if len(addrs) > 0 {
return addrs[0], nil
}
}
return "", nil
}

View File

@@ -22,21 +22,21 @@ type UserNode struct {
}
type UserNodeOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
UniqueId interface{} // 唯一ID
Secret interface{} // 密钥
Name interface{} // 名称
Description interface{} // 描述
Http interface{} // 监听的HTTP配置
Https interface{} // 监听的HTTPS配置
AccessAddrs interface{} // 外部访问地址
Order interface{} // 排序
State interface{} // 状态
CreatedAt interface{} // 创建时间
AdminId interface{} // 管理员ID
Weight interface{} // 权重
Status interface{} // 运行状态
Id any // ID
IsOn any // 是否启用
UniqueId any // 唯一ID
Secret any // 密钥
Name any // 名称
Description any // 描述
Http any // 监听的HTTP配置
Https any // 监听的HTTPS配置
AccessAddrs any // 外部访问地址
Order any // 排序
State any // 状态
CreatedAt any // 创建时间
AdminId any // 管理员ID
Weight any // 权重
Status any // 运行状态
}
func NewUserNodeOperator() *UserNodeOperator {

View File

@@ -0,0 +1,21 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
import (
"errors"
"github.com/iwind/TeaGo/types"
)
type BaseResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (this *BaseResponse) IsValid() bool {
return this.Code == 200
}
func (this *BaseResponse) Error() error {
return errors.New("code: " + types.String(this.Code) + ", message: " + this.Message)
}

View File

@@ -0,0 +1,11 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type CreateNSRecordResponse struct {
BaseResponse
Data struct {
NSRecordId int64 `json:"nsRecordId"`
} `json:"data"`
}

View File

@@ -0,0 +1,14 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type FindAllNSRoutesResponse struct {
BaseResponse
Data struct {
NSRoutes []struct {
Name string `json:"name"`
Code string `json:"code"`
} `json:"nsRoutes"`
} `json:"data"`
}

View File

@@ -0,0 +1,14 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type FindDomainWithNameResponse struct {
BaseResponse
Data struct {
NSDomain struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
} `json:"data"`
}

View File

@@ -0,0 +1,21 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type FindNSRecordWithNameAndTypeResponse struct {
BaseResponse
Data struct {
NSRecord struct {
Id int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"`
TTL int32 `json:"ttl"`
NSRoutes []struct {
Name string `json:"name"`
Code string `json:"code"`
} `json:"nsRoutes"`
} `json:"nsRecord"`
}
}

View File

@@ -0,0 +1,12 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type GetAPIAccessToken struct {
BaseResponse
Data struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expiresAt"`
} `json:"data"`
}

View File

@@ -0,0 +1,8 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type ResponseInterface interface {
IsValid() bool
Error() error
}

View File

@@ -0,0 +1,16 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type ListNSDomainsResponse struct {
BaseResponse
Data struct {
NSDomains []struct {
Id int64 `json:"id"`
Name string `json:"name"`
IsOn bool `json:"isOn"`
IsDeleted bool `json:"isDeleted"`
} `json:"nsDomains"`
} `json:"data"`
}

View File

@@ -0,0 +1,21 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type ListNSRecordsResponse struct {
BaseResponse
Data struct {
NSRecords []struct {
Id int64 `json:"id"`
Name string `json:"name"`
Value string `json:"value"`
TTL int32 `json:"ttl"`
Type string `json:"type"`
NSRoutes []struct {
Name string `json:"name"`
Code string `json:"code"`
} `json:"nsRoutes"`
} `json:"nsRecords"`
} `json:"data"`
}

View File

@@ -0,0 +1,7 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type SuccessResponse struct {
BaseResponse
}

View File

@@ -0,0 +1,7 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package edgeapi
type UpdateNSRecordResponse struct {
BaseResponse
}

View File

@@ -130,14 +130,16 @@ func (this *APINode) Start() {
}
// 数据库通知启动
this.setProgress("DATABASE", "正在建立数据库模型")
logs.Println("[API_NODE]notify ready ...")
dbs.NotifyReady()
// 设置时区
this.setProgress("TIMEZONE", "正在设置时区")
this.setupTimeZone()
// 读取配置
this.setProgress("DATABASE", "加载API配置")
this.setProgress("DATABASE", "正在加载API配置")
logs.Println("[API_NODE]reading api config ...")
config, err := configs.SharedAPIConfig()
if err != nil {
@@ -384,6 +386,19 @@ func (this *APINode) setupDB() error {
return err
}
// 检查是否为root用户
config, _ := db.Config()
if config == nil {
return nil
}
dsnConfig, err := mysql.ParseDSN(config.Dsn)
if err != nil || dsnConfig == nil {
return err
}
if dsnConfig.User != "root" {
return nil
}
// 设置Innodb事务提交模式
{
result, err := db.FindOne("SHOW VARIABLES WHERE variable_name='innodb_flush_log_at_trx_commit'")

View File

@@ -367,11 +367,6 @@ func (this *APINode) registerServices(server *grpc.Server) {
pb.RegisterServerBillServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&services.UserNodeService{}).(*services.UserNodeService)
pb.RegisterUserNodeServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&services.LoginService{}).(*services.LoginService)
pb.RegisterLoginServiceServer(server, instance)

View File

@@ -1,304 +0,0 @@
package services
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"google.golang.org/grpc/metadata"
"time"
)
type UserNodeService struct {
BaseService
}
// CreateUserNode 创建用户节点
func (this *UserNodeService) CreateUserNode(ctx context.Context, req *pb.CreateUserNodeRequest) (*pb.CreateUserNodeResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
nodeId, err := models.SharedUserNodeDAO.CreateUserNode(tx, req.Name, req.Description, req.HttpJSON, req.HttpsJSON, req.AccessAddrsJSON, req.IsOn)
if err != nil {
return nil, err
}
return &pb.CreateUserNodeResponse{UserNodeId: nodeId}, nil
}
// UpdateUserNode 修改用户节点
func (this *UserNodeService) UpdateUserNode(ctx context.Context, req *pb.UpdateUserNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedUserNodeDAO.UpdateUserNode(tx, req.UserNodeId, req.Name, req.Description, req.HttpJSON, req.HttpsJSON, req.AccessAddrsJSON, req.IsOn)
if err != nil {
return nil, err
}
return this.Success()
}
// DeleteUserNode 删除用户节点
func (this *UserNodeService) DeleteUserNode(ctx context.Context, req *pb.DeleteUserNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedUserNodeDAO.DisableUserNode(tx, req.UserNodeId)
if err != nil {
return nil, err
}
return this.Success()
}
// FindAllEnabledUserNodes 列出所有可用用户节点
func (this *UserNodeService) FindAllEnabledUserNodes(ctx context.Context, req *pb.FindAllEnabledUserNodesRequest) (*pb.FindAllEnabledUserNodesResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
nodes, err := models.SharedUserNodeDAO.FindAllEnabledUserNodes(tx)
if err != nil {
return nil, err
}
result := []*pb.UserNode{}
for _, node := range nodes {
accessAddrs, err := node.DecodeAccessAddrStrings()
if err != nil {
return nil, err
}
result = append(result, &pb.UserNode{
Id: int64(node.Id),
IsOn: node.IsOn,
UniqueId: node.UniqueId,
Secret: node.Secret,
Name: node.Name,
Description: node.Description,
HttpJSON: node.Http,
HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
})
}
return &pb.FindAllEnabledUserNodesResponse{UserNodes: result}, nil
}
// CountAllEnabledUserNodes 计算用户节点数量
func (this *UserNodeService) CountAllEnabledUserNodes(ctx context.Context, req *pb.CountAllEnabledUserNodesRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
count, err := models.SharedUserNodeDAO.CountAllEnabledUserNodes(tx)
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}
// ListEnabledUserNodes 列出单页的用户节点
func (this *UserNodeService) ListEnabledUserNodes(ctx context.Context, req *pb.ListEnabledUserNodesRequest) (*pb.ListEnabledUserNodesResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
nodes, err := models.SharedUserNodeDAO.ListEnabledUserNodes(tx, req.Offset, req.Size)
if err != nil {
return nil, err
}
result := []*pb.UserNode{}
for _, node := range nodes {
accessAddrs, err := node.DecodeAccessAddrStrings()
if err != nil {
return nil, err
}
result = append(result, &pb.UserNode{
Id: int64(node.Id),
IsOn: node.IsOn,
UniqueId: node.UniqueId,
Secret: node.Secret,
Name: node.Name,
Description: node.Description,
HttpJSON: node.Http,
HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
StatusJSON: node.Status,
})
}
return &pb.ListEnabledUserNodesResponse{UserNodes: result}, nil
}
// FindEnabledUserNode 根据ID查找节点
func (this *UserNodeService) FindEnabledUserNode(ctx context.Context, req *pb.FindEnabledUserNodeRequest) (*pb.FindEnabledUserNodeResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
node, err := models.SharedUserNodeDAO.FindEnabledUserNode(tx, req.UserNodeId)
if err != nil {
return nil, err
}
if node == nil {
return &pb.FindEnabledUserNodeResponse{UserNode: nil}, nil
}
accessAddrs, err := node.DecodeAccessAddrStrings()
if err != nil {
return nil, err
}
result := &pb.UserNode{
Id: int64(node.Id),
IsOn: node.IsOn,
UniqueId: node.UniqueId,
Secret: node.Secret,
Name: node.Name,
Description: node.Description,
HttpJSON: node.Http,
HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
}
return &pb.FindEnabledUserNodeResponse{UserNode: result}, nil
}
// FindCurrentUserNode 获取当前用户节点的版本
func (this *UserNodeService) FindCurrentUserNode(ctx context.Context, req *pb.FindCurrentUserNodeRequest) (*pb.FindCurrentUserNodeResponse, error) {
_, err := this.ValidateUserNode(ctx, false)
if err != nil {
return nil, err
}
var tx = this.NullTx()
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errors.New("context: need 'nodeId'")
}
nodeIds := md.Get("nodeid")
if len(nodeIds) == 0 {
return nil, errors.New("invalid 'nodeId'")
}
nodeId := nodeIds[0]
node, err := models.SharedUserNodeDAO.FindEnabledUserNodeWithUniqueId(tx, nodeId)
if err != nil {
return nil, err
}
if node == nil {
return &pb.FindCurrentUserNodeResponse{UserNode: nil}, nil
}
accessAddrs, err := node.DecodeAccessAddrStrings()
if err != nil {
return nil, err
}
result := &pb.UserNode{
Id: int64(node.Id),
IsOn: node.IsOn,
UniqueId: node.UniqueId,
Secret: node.Secret,
Name: node.Name,
Description: node.Description,
HttpJSON: node.Http,
HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
}
return &pb.FindCurrentUserNodeResponse{UserNode: result}, nil
}
// UpdateUserNodeStatus 更新节点状态
func (this *UserNodeService) UpdateUserNodeStatus(ctx context.Context, req *pb.UpdateUserNodeStatusRequest) (*pb.RPCSuccess, error) {
// 校验节点
_, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeUser)
if err != nil {
return nil, err
}
if req.UserNodeId > 0 {
nodeId = req.UserNodeId
}
if nodeId <= 0 {
return nil, errors.New("'nodeId' should be greater than 0")
}
var tx = this.NullTx()
// 修改时间戳
var nodeStatus = &nodeconfigs.NodeStatus{}
err = json.Unmarshal(req.StatusJSON, nodeStatus)
if err != nil {
return nil, errors.New("decode node status json failed: " + err.Error())
}
nodeStatus.UpdatedAt = time.Now().Unix()
// 保存
err = models.SharedUserNodeDAO.UpdateNodeStatus(tx, nodeId, nodeStatus)
if err != nil {
return nil, err
}
return this.Success()
}
// CountAllEnabledUserNodesWithSSLCertId 计算使用某个SSL证书的用户节点数量
func (this *UserNodeService) CountAllEnabledUserNodesWithSSLCertId(ctx context.Context, req *pb.CountAllEnabledUserNodesWithSSLCertIdRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
policyIds, err := models.SharedSSLPolicyDAO.FindAllEnabledPolicyIdsWithCertId(tx, req.SslCertId)
if err != nil {
return nil, err
}
if len(policyIds) == 0 {
return this.SuccessCount(0)
}
count, err := models.SharedUserNodeDAO.CountAllEnabledUserNodesWithSSLPolicyIds(tx, policyIds)
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ package setup
import (
"errors"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
@@ -137,12 +138,22 @@ func (this *SQLDump) Dump(db *dbs.DB) (result *SQLDumpResult, err error) {
func (this *SQLDump) Apply(db *dbs.DB, newResult *SQLDumpResult, showLog bool) (ops []string, err error) {
// 设置Innodb事务提交模式
{
result, err := db.FindOne("SHOW VARIABLES WHERE variable_name='innodb_flush_log_at_trx_commit'")
if err == nil && result != nil {
var oldValue = result.GetInt("Value")
if oldValue == 1 {
_, _ = db.Exec("SET GLOBAL innodb_flush_log_at_trx_commit=2")
// 检查是否为root用户
config, _ := db.Config()
if config == nil {
return nil, nil
}
dsnConfig, err := mysql.ParseDSN(config.Dsn)
if err != nil || dsnConfig == nil {
return nil, err
}
if dsnConfig.User == "root" {
result, err := db.FindOne("SHOW VARIABLES WHERE variable_name='innodb_flush_log_at_trx_commit'")
if err == nil && result != nil {
var oldValue = result.GetInt("Value")
if oldValue == 1 {
_, _ = db.Exec("SET GLOBAL innodb_flush_log_at_trx_commit=2")
}
}
}
}

View File

@@ -76,7 +76,7 @@ func TestSQLDump_Apply(t *testing.T) {
db2, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql",
Dsn: "root:123456@tcp(192.168.2.60:3306)/db_edge_new?charset=utf8mb4&timeout=30s",
Dsn: "edge:123456@tcp(192.168.2.60:3306)/db_edge_new?charset=utf8mb4&timeout=30s",
Prefix: "edge",
})
if err != nil {

View File

@@ -111,7 +111,8 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
shortName := teaconst.SystemdServiceName
longName := "GoEdge API" // TODO 将来可以修改
desc := `# Provides: ` + shortName + `
desc := `### BEGIN INIT INFO
# Provides: ` + shortName + `
# Required-Start: $all
# Required-Stop:
# Default-Start: 2 3 4 5