import sources

This commit is contained in:
Олег Бородин
2024-07-30 09:49:53 +02:00
commit e9d4d1ef07
51 changed files with 15484 additions and 0 deletions

162
internal/config/config.go Normal file
View File

@@ -0,0 +1,162 @@
package config
import (
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"certmanager/pkg/client"
yaml "gopkg.in/yaml.v3"
)
const (
defaultUsername = "worker"
defaultPassword = "worker"
defaultHostname = "localhost"
configFilename = "certmanagerd.yaml"
logFilename = "certmanager.log"
pidFilename = "certmanager.pid"
)
var (
buildVersion = "NONE"
)
type WserviceConfig struct {
PortNum int `json:"port" yaml:"port"`
}
type GserviceConfig struct {
PortNum int `json:"port" yaml:"port"`
}
type AuthConfig struct {
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
}
type Config struct {
Wservice WserviceConfig `json:"wservice" yaml:"wservice"`
Gservice GserviceConfig `json:"gservice" yaml:"gservice"`
Auths []AuthConfig `json:"auths" yaml:"auths"`
Hostname string `json:"hostname" yaml:"hostname"`
Debug bool `json:"debug" yaml:"debug"`
Build string `json:"build" yaml:"build"`
LogPath string `json:"logfile" yaml:"logfile"`
RunPath string `json:"runfile" yaml:"runfile"`
DataDir string `json:"datadir" yaml:"datadir"`
Daemon bool `json:"daemon" yaml:"daemon"`
}
func NewConfig() *Config {
conf := &Config{
Gservice: GserviceConfig{
PortNum: client.DefaultGrpcPort,
},
Wservice: WserviceConfig{
PortNum: client.DefaultWrpcPort,
},
Auths: make([]AuthConfig, 0),
DataDir: datadirPath,
Debug: false,
Hostname: defaultHostname,
Build: buildVersion,
Daemon: true,
}
conf.LogPath = filepath.Join(logdirPath, logFilename)
conf.RunPath = filepath.Join(rundirPath, pidFilename)
return conf
}
func (conf *Config) ReadFile() error {
var err error
configPath := filepath.Join(confdirPath, configFilename)
confBytes, err := os.ReadFile(configPath)
if err != nil {
return err
}
err = yaml.Unmarshal(confBytes, conf)
if err != nil {
return err
}
conf.Normalize()
err = conf.Validate()
if err != nil {
return err
}
return nil
}
func (conf *Config) ReadEnv() error {
var err error
return err
}
func (conf *Config) ReadOpts() error {
var err error
exeName := filepath.Base(os.Args[0])
flag.IntVar(&conf.Wservice.PortNum, "wport", conf.Wservice.PortNum, "wrpc listen port")
flag.IntVar(&conf.Gservice.PortNum, "gport", conf.Gservice.PortNum, "grpc listen port")
flag.BoolVar(&conf.Daemon, "daemon", conf.Daemon, "run as daemon")
flag.BoolVar(&conf.Debug, "debug", conf.Debug, "on debug mode")
help := func() {
fmt.Println("")
fmt.Printf("Usage: %s [option]\n", exeName)
fmt.Println("")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("")
}
flag.Usage = help
flag.Parse()
return err
}
func (conf *Config) Yaml() (string, error) {
var err error
var res string
yamlBytes, err := yaml.Marshal(conf)
if err != nil {
return res, err
}
res = string(yamlBytes)
return res, err
}
func (conf *Config) Normalize() {
if conf.Gservice.PortNum == 0 {
conf.Gservice.PortNum = client.DefaultGrpcPort
}
if conf.Wservice.PortNum == 0 {
conf.Wservice.PortNum = client.DefaultWrpcPort
}
}
func (conf *Config) Validate() error {
var err []error
for i := range conf.Auths {
if conf.Auths[i].Username == "" {
err = append(err, errors.New("Username must be set"))
}
if conf.Auths[i].Password == "" {
err = append(err, errors.New("Password must be set"))
}
}
return errors.Join(err...)
}

9
internal/config/path.go Normal file
View File

@@ -0,0 +1,9 @@
package config
const (
confdirPath = "/home/ziggi/projects/certman/etc/certmanager"
rundirPath = "/home/ziggi/projects/certman/tmp.run"
logdirPath = "/home/ziggi/projects/certman/tmp.log"
datadirPath = "/home/ziggi/projects/certman/tmp.data"
)

View File

@@ -0,0 +1,9 @@
package config
const (
confdirPath = "@srv_confdir@"
rundirPath = "@srv_rundir@"
logdirPath = "@srv_logdir@"
datadirPath = "@srv_datadir@"
)

View File

@@ -0,0 +1,70 @@
package database
import (
"path/filepath"
"certmanager/pkg/logger"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
const schema = `
--- DROP TABLE IF EXISTS manifests;
CREATE TABLE IF NOT EXISTS manifests (
id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
reference VARCHAR(255) NOT NULL,
contentType VARCHAR(255) NOT NULL,
payload VARCHAR(4096) NOT NULL
);
CREATE INDEX IF NOT EXISTS manifest_index
ON manifests(name, reference);
CREATE TABLE IF NOT EXISTS blobs (
id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
digest VARCHAR(255) NOT NULL,
used INTEGER
);
CREATE INDEX IF NOT EXISTS blobs_index
ON blobs(name, digest);
`
type Database struct {
datapath string
db *sqlx.DB
log *logger.Logger
}
func NewDatabase(datapath string) (*Database, error) {
var err error
db := &Database{
datapath: datapath,
}
db.log = logger.NewLogger("database")
return db, err
}
func (db *Database) InitDatabase() error {
var err error
dbPath := filepath.Join(db.datapath, "certmanager.db")
db.db, err = sqlx.Open("sqlite3", dbPath)
if err != nil {
return err
}
err = db.db.Ping()
if err != nil {
return err
}
_, err = db.db.Exec(schema)
if err != nil {
return err
}
return err
}

View File

@@ -0,0 +1,3 @@
package descriptor

View File

@@ -0,0 +1,31 @@
package handler
import (
"certmanager/api/certmanagercontrol"
"certmanager/internal/logic"
"certmanager/pkg/logger"
"google.golang.org/grpc"
)
type HandlerConfig struct {
Logic *logic.Logic
}
type Handler struct {
certmanagercontrol.UnimplementedControlServer
lg *logic.Logic
log *logger.Logger
}
func NewHandler(conf *HandlerConfig) *Handler {
hand := Handler{
lg: conf.Logic,
}
hand.log = logger.NewLogger("ghandler")
return &hand
}
func (hand *Handler) Register(gsrv *grpc.Server) {
certmanagercontrol.RegisterControlServer(gsrv, hand)
}

View File

@@ -0,0 +1,14 @@
package handler
import (
"context"
"certmanager/api/certmanagercontrol"
)
func (hand *Handler) GetStatus(ctx context.Context, req *certmanagercontrol.GetStatusParams) (*certmanagercontrol.GetStatusResult, error) {
var err error
hand.log.Debugf("Handle getStatus request")
res, err := hand.lg.GetStatus(ctx, req)
return res, err
}

View File

@@ -0,0 +1,137 @@
package service
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"certmanager/internal/grpc/handler"
"certmanager/internal/logic"
"certmanager/pkg/logger"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type ServiceConfig struct {
Handler *handler.Handler
Logic *logic.Logic
PortNum int
Hostname string
X509Cert []byte
X509Key []byte
}
type Service struct {
gsrv *grpc.Server
hand *handler.Handler
lg *logic.Logic
log *logger.Logger
portnum int
hostname string
username string
password string
x509Cert []byte
x509Key []byte
}
func NewService(conf *ServiceConfig) *Service {
svc := Service{
hand: conf.Handler,
lg: conf.Logic,
portnum: conf.PortNum,
hostname: conf.Hostname,
x509Cert: conf.X509Cert,
x509Key: conf.X509Key,
}
svc.log = logger.NewLogger("gservice")
return &svc
}
func (svc *Service) Run() error {
var err error
svc.log.Infof("Service run")
listenSpec := fmt.Sprintf(":%d", svc.portnum)
listener, err := net.Listen("tcp", listenSpec)
if err != nil {
return err
}
tlsCert, err := tls.X509KeyPair(svc.x509Cert, svc.x509Key)
if err != nil {
return err
}
tlsConfig := tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.NoClientCert,
InsecureSkipVerify: true,
}
tlsCredentials := credentials.NewTLS(&tlsConfig)
if err != nil {
return err
}
gsrvOpts := []grpc.ServerOption{
grpc.Creds(tlsCredentials),
grpc.UnaryInterceptor(svc.authInterceptor),
}
svc.gsrv = grpc.NewServer(gsrvOpts...)
svc.hand.Register(svc.gsrv)
svc.log.Infof("Service listening at %v", listener.Addr())
err = svc.gsrv.Serve(listener)
if err != nil {
return err
}
return err
}
func (svc *Service) authInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
svc.log.Debugf("Called unary interceptor with method: %v", info.FullMethod)
meta, _ := metadata.FromIncomingContext(ctx)
svc.log.Debugf("Reqest username: %s", meta["username"])
svc.log.Debugf("Reqest password: %s", meta["password"])
usernameArr := meta["username"]
passwordArr := meta["password"]
if len(usernameArr) == 0 || len(passwordArr) == 0 {
err := status.Errorf(codes.PermissionDenied, "Empty auth data")
return nil, err
}
username := meta["username"][0]
password := meta["password"][0]
if !svc.lg.ValidateUser(username, password) {
err := status.Errorf(codes.PermissionDenied, "Wrong auth data")
return nil, err
}
reqBinary, err := json.Marshal(req)
if err == nil {
svc.log.Debugf("Request: %s", string(reqBinary))
}
return handler(ctx, req)
}
func (svc *Service) logInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
var err error
svc.log.Debugf("Called unary interceptor with method: %v", info.FullMethod)
reqBinary, err := json.Marshal(req)
if err == nil {
svc.log.Debugf("Request: %f", string(reqBinary))
}
return handler(ctx, req)
}
func (svc *Service) Stop() {
svc.log.Infof("Stopping service")
svc.gsrv.GracefulStop()
}

10
internal/logic/auth.go Normal file
View File

@@ -0,0 +1,10 @@
package logic
func (lg *Logic) ValidateUser(username, password string) bool {
for i := range lg.auths {
if username == lg.auths[i].Username && password == lg.auths[i].Password {
return true
}
}
return false
}

28
internal/logic/logic.go Normal file
View File

@@ -0,0 +1,28 @@
package logic
import (
"certmanager/internal/config"
"certmanager/internal/database"
"certmanager/pkg/logger"
)
type LogicConfig struct {
Database *database.Database
Auths []config.AuthConfig
}
type Logic struct {
auths []config.AuthConfig
log *logger.Logger
db *database.Database
}
func NewLogic(conf *LogicConfig) (*Logic, error) {
var err error
lg := &Logic{
db: conf.Database,
auths: conf.Auths,
}
lg.log = logger.NewLogger("logic")
return lg, err
}

15
internal/logic/status.go Normal file
View File

@@ -0,0 +1,15 @@
package logic
import (
"context"
"certmanager/api/certmanagercontrol"
)
func (lg *Logic) GetStatus(ctx context.Context, nReq *certmanagercontrol.GetStatusParams) (*certmanagercontrol.GetStatusResult, error) {
var err error
nRes := &certmanagercontrol.GetStatusResult{
Message: "Hello",
}
return nRes, err
}

294
internal/server/server.go Normal file
View File

@@ -0,0 +1,294 @@
package server
import (
"os"
"os/signal"
"syscall"
"os/user"
"path/filepath"
"strconv"
"certmanager/internal/config"
"certmanager/internal/database"
"certmanager/internal/logic"
"certmanager/pkg/aux509"
"certmanager/pkg/logger"
ghandler "certmanager/internal/grpc/handler"
gservice "certmanager/internal/grpc/service"
whandler "certmanager/internal/wrpc/handler"
wservice "certmanager/internal/wrpc/service"
)
type Server struct {
conf *config.Config
lg *logic.Logic
wsvc *wservice.Service
whand *whandler.Handler
gsvc *gservice.Service
ghand *ghandler.Handler
log *logger.Logger
db *database.Database
x509cert []byte
x509key []byte
x509caCert []byte
x509caKey []byte
}
func NewServer() (*Server, error) {
var err error
srv := &Server{}
srv.log = logger.NewLogger("server")
return srv, err
}
func (srv *Server) Configure() error {
var err error
srv.conf = config.NewConfig()
err = srv.conf.ReadFile()
if err != nil {
return err
}
err = srv.conf.ReadEnv()
if err != nil {
return err
}
err = srv.conf.ReadOpts()
if err != nil {
return err
}
return err
}
func (srv *Server) Build() error {
var err error
srv.log.Infof("Build server")
// Create X509 certs
srv.x509caCert, srv.x509caKey, err = aux509.CreateX509CACert(srv.conf.Hostname)
if err != nil {
return err
}
srv.x509cert, srv.x509key, err = aux509.CreateX509Cert(srv.conf.Hostname, srv.x509caKey)
if err != nil {
return err
}
// Create database
db, err := database.NewDatabase(srv.conf.DataDir)
if err != nil {
return err
}
srv.db = db
// Create logic
logicConfig := &logic.LogicConfig{
Auths: srv.conf.Auths,
Database: srv.db,
}
srv.lg, err = logic.NewLogic(logicConfig)
if err != nil {
return err
}
// Create whandler
whandlerConfig := &whandler.HandlerConfig{
Logic: srv.lg,
}
srv.whand, err = whandler.NewHandler(whandlerConfig)
if err != nil {
return err
}
// Create wservice
wserviceConfig := &wservice.ServiceConfig{
PortNum: srv.conf.Wservice.PortNum,
Hostname: srv.conf.Hostname,
Handler: srv.whand,
X509Cert: srv.x509cert,
X509Key: srv.x509key,
}
srv.wsvc, err = wservice.NewService(wserviceConfig)
if err != nil {
return err
}
err = srv.wsvc.Build()
if err != nil {
return err
}
// Create ghandler
ghandlerConfig := &ghandler.HandlerConfig{
Logic: srv.lg,
}
srv.ghand = ghandler.NewHandler(ghandlerConfig)
if err != nil {
return err
}
// Create gservice
gserviceConfig := &gservice.ServiceConfig{
PortNum: srv.conf.Gservice.PortNum,
Hostname: srv.conf.Hostname,
Handler: srv.ghand,
Logic: srv.lg,
X509Cert: srv.x509cert,
X509Key: srv.x509key,
}
srv.gsvc = gservice.NewService(gserviceConfig)
if err != nil {
return err
}
return err
}
func (srv *Server) Run() error {
var err error
// Log configuration
yamlConfig, err := srv.conf.Yaml()
if err != nil {
return err
}
srv.log.Debugf("Server configuration:\n%s\n", yamlConfig)
// Show current user
currUser, err := user.Current()
if err != nil {
return err
}
srv.log.Infof("Running server as user %s", currUser.Username)
sigs := make(chan os.Signal, 1)
gdone := make(chan error, 1)
wdone := make(chan error, 1)
// Run wservice
startWservice := func(svc *wservice.Service, done chan error) {
err = svc.Run()
if err != nil {
srv.log.Errorf("Service error: %v", err)
done <- err
}
}
go startWservice(srv.wsvc, wdone)
// Run gservice
startGservice := func(svc *gservice.Service, done chan error) {
err = svc.Run()
if err != nil {
srv.log.Errorf("Service error: %v", err)
done <- err
}
}
go startGservice(srv.gsvc, gdone)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
var signal os.Signal
select {
case signal = <-sigs:
srv.log.Infof("Services stopped by signal: %v", signal)
srv.wsvc.Stop()
srv.gsvc.Stop()
case err = <-gdone:
srv.log.Infof("Service stopped by gservice error: %v", err)
srv.wsvc.Stop()
case err = <-wdone:
srv.log.Infof("Service stopped by wservice error: %v", err)
srv.gsvc.Stop()
}
return err
}
func (srv *Server) PseudoFork() error {
const successExit int = 0
var keyEnv string = "IMX0LTSELMRF8KASWER"
var err error
_, isChild := os.LookupEnv(keyEnv)
switch {
case !isChild:
os.Setenv(keyEnv, "TRUE")
procAttr := syscall.ProcAttr{}
cwd, err := os.Getwd()
if err != nil {
return err
}
var sysFiles = make([]uintptr, 3)
sysFiles[0] = uintptr(syscall.Stdin)
sysFiles[1] = uintptr(syscall.Stdout)
sysFiles[2] = uintptr(syscall.Stderr)
procAttr.Files = sysFiles
procAttr.Env = os.Environ()
procAttr.Dir = cwd
_, err = syscall.ForkExec(os.Args[0], os.Args, &procAttr)
if err != nil {
return err
}
os.Exit(successExit)
case isChild:
_, err = syscall.Setsid()
if err != nil {
return err
}
}
os.Unsetenv(keyEnv)
return err
}
func (srv *Server) Daemonize() error {
var err error
if srv.conf.Daemon {
// Restart process process
err = srv.PseudoFork()
if err != nil {
return err
}
// Redirect stdin
nullFile, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
return err
}
err = syscall.Dup2(int(nullFile.Fd()), int(os.Stdin.Fd()))
if err != nil {
return err
}
// Redirect stderr and stout
logdir := filepath.Dir(srv.conf.LogPath)
err = os.MkdirAll(logdir, 0750)
if err != nil {
return err
}
logFile, err := os.OpenFile(srv.conf.LogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640)
if err != nil {
return err
}
err = syscall.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
if err != nil {
return err
}
err = syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
if err != nil {
return err
}
// Write process ID
rundir := filepath.Dir(srv.conf.RunPath)
err = os.MkdirAll(rundir, 0750)
if err != nil {
return err
}
pidFile, err := os.OpenFile(srv.conf.RunPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer pidFile.Close()
currPid := os.Getpid()
_, err = pidFile.WriteString(strconv.Itoa(currPid))
if err != nil {
return err
}
}
return err
}

View File

@@ -0,0 +1,32 @@
package handler
import (
"errors"
"fmt"
"certmanager/pkg/auxhttp"
"github.com/gin-gonic/gin"
)
func (hand *Handler) BasicAuth(gctx *gin.Context) {
var err error
authHeader := gctx.Request.Header.Get("Authorization")
if authHeader == "" {
err = errors.New("Cannot found autentification data")
auxhttp.SendError(gctx, err)
return
}
username, password, err := auxhttp.ParseAuthBasicHeader(authHeader)
if err != nil {
err = fmt.Errorf("Authorization error: %s", err)
auxhttp.SendError(gctx, err)
return
}
if !hand.lg.ValidateUser(username, password) {
err = errors.New("Incorrect autentification data")
auxhttp.SendError(gctx, err)
return
}
gctx.Next()
}

View File

@@ -0,0 +1,24 @@
package handler
import (
"certmanager/internal/logic"
"certmanager/pkg/logger"
)
type HandlerConfig struct {
Logic *logic.Logic
}
type Handler struct {
log *logger.Logger
lg *logic.Logic
}
func NewHandler(conf *HandlerConfig) (*Handler, error) {
var err error
hand := &Handler{
log: logger.NewLogger("whandler"),
lg: conf.Logic,
}
return hand, err
}

View File

@@ -0,0 +1,31 @@
package handler
import (
"certmanager/api/certmanagercontrol"
"certmanager/pkg/auxhttp"
"github.com/gin-gonic/gin"
)
func (hand *Handler) GetStatus(gctx *gin.Context) {
var err error
nReq := &certmanagercontrol.GetStatusParams{}
// Bind request
err = gctx.ShouldBind(nReq)
if err != nil {
hand.log.Errorf("Cannot bind: %v", err)
auxhttp.SendError(gctx, err)
return
}
// Call logic
ctx := gctx.Request.Context()
lgRes, err := hand.lg.GetStatus(ctx, nReq)
if err != nil {
hand.log.Errorf("Got error: %v", err)
auxhttp.SendError(gctx, err)
return
}
// Send result
nRes := lgRes
auxhttp.SendResult(gctx, nRes)
}

View File

@@ -0,0 +1,132 @@
package service
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"time"
"certmanager/internal/wrpc/handler"
"certmanager/pkg/auxgin"
"certmanager/pkg/logger"
"certmanager/pkg/auxhttp"
"github.com/gin-gonic/gin"
)
const (
httpTimeout = 360
)
type ServiceConfig struct {
Handler *handler.Handler
PortNum int
Hostname string
X509Cert []byte
X509Key []byte
}
type Service struct {
hand *handler.Handler
hsrv *http.Server
log *logger.Logger
engine *gin.Engine
portnum int
hostname string
x509Cert []byte
x509Key []byte
}
func NewService(conf *ServiceConfig) (*Service, error) {
var err error
svc := &Service{
hand: conf.Handler,
portnum: conf.PortNum,
hostname: conf.Hostname,
x509Cert: conf.X509Cert,
x509Key: conf.X509Key,
}
svc.log = logger.NewLogger("wservice")
return svc, err
}
func (svc *Service) Build() error {
var err error
svc.log.Debugf("Build service")
gin.SetMode(gin.ReleaseMode)
gin.DisableConsoleColor()
svc.engine = gin.New()
svc.engine.Use(gin.Recovery())
svc.engine.Use(auxgin.CorsMiddleware())
svc.engine.Use(auxgin.LogMiddleware())
svc.engine.Use(auxgin.RequestLogMiddleware())
apiGroup := svc.engine.Group("api")
v1Group := apiGroup.Group("v1")
{
statusGroup := v1Group.Group("status")
statusGroup.POST("get", svc.hand.GetStatus)
/*
forwardingGroup := v1Group.Group("forwarding")
forwardingGroup.POST("create", svc.hand.CreateForwarding)
forwardingGroup.POST("list", svc.hand.ListForwardings)
forwardingGroup.POST("delete", svc.hand.DeleteForwarding)
defaultsGroup := v1Group.Group("defaults")
defaultsGroup.POST("set", svc.hand.SetDefaults)
defaultsGroup.POST("get", svc.hand.GetDefaults)
proxyGroup := v1Group.Group("proxy")
proxyGroup.POST("reset", svc.hand.ResetProxy)
*/
}
noRouteFunc := func(gctx *gin.Context) {
err := fmt.Errorf("No route")
auxhttp.SendError(gctx, err)
}
svc.engine.NoRoute(noRouteFunc)
tlsCert, err := tls.X509KeyPair(svc.x509Cert, svc.x509Key)
if err != nil {
return err
}
tlsConfig := tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.NoClientCert,
InsecureSkipVerify: true,
}
listenAddr := fmt.Sprintf(":%d", svc.portnum)
svc.hsrv = &http.Server{
Addr: listenAddr,
Handler: svc.engine,
TLSConfig: &tlsConfig,
}
return err
}
func (svc *Service) Run() error {
var err error
for _, route := range svc.engine.Routes() {
svc.log.Debugf("The route is registered: %s %s", route.Method, route.Path)
}
svc.log.Infof("Service listening at %d port", svc.portnum)
err = svc.hsrv.ListenAndServeTLS("", "")
if err != nil {
return err
}
return err
}
func (svc *Service) Stop() {
svc.log.Infof("Stopping service")
if svc.hsrv != nil {
downWaiting := 5 * time.Second
ctx, _ := context.WithTimeout(context.Background(), downWaiting)
svc.hsrv.Shutdown(ctx)
}
}