package server import ( "io/ioutil" "os" "os/signal" "os/user" "path/filepath" "strconv" "syscall" "time" "mbase/app/config" "mbase/app/maindb" "mbase/app/operator" "mbase/pkg/aux509" "mbase/pkg/descr" "mbase/pkg/logger" "mbase/pkg/netacl" handler "mbase/app/handler" service "mbase/app/service" "sigs.k8s.io/yaml" ) type Server struct { conf *config.Config oper *operator.Operator svc *service.Service hand *handler.Handler log *logger.Logger nacl *netacl.NetACL db *maindb.Database x509cert []byte x509key []byte state descr.Server sfile string } 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 } srv.sfile = filepath.Join(srv.conf.DataDir, "mbase.yaml") return err } func (srv *Server) LoadState() error { var err error _, err = os.Stat(srv.sfile) if os.IsNotExist(err) { err = nil return err } file, err := os.Open(srv.sfile) if err != nil { return err } defer file.Close() stateBytes, err := ioutil.ReadAll(file) if err != nil { return err } err = yaml.Unmarshal(stateBytes, &srv.state) if err != nil { return err } return err } func (srv *Server) SaveState() error { var err error file, err := os.OpenFile(srv.sfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640) if err != nil { return err } defer file.Close() srv.state.UpdatedAt = time.Now().Format(time.RFC3339) if srv.state.CreatedAt == "" { srv.state.CreatedAt = srv.state.UpdatedAt } stateBytes, err := yaml.Marshal(srv.state) if err != nil { return err } _, err = file.Write(stateBytes) if err != nil { return err } return err } func (srv *Server) Build() error { var err error srv.log.Infof("Build server") // Mkdir log and data dir srv.log.Infof("Create %s dir", srv.conf.DataDir) err = os.MkdirAll(srv.conf.DataDir, 0750) if err != nil { return err } if srv.conf.Daemon { logDir := filepath.Dir(srv.conf.LogPath) srv.log.Infof("Create %s dir", logDir) err = os.MkdirAll(logDir, 0750) if err != nil { return err } runDir := filepath.Dir(srv.conf.RunPath) srv.log.Infof("Create %s dir", runDir) err = os.MkdirAll(runDir, 0750) if err != nil { return err } } // Create X509 certs srv.x509cert, srv.x509key, err = aux509.CreateX509SelfSignedCert(srv.conf.Hostname) if err != nil { return err } // Load state err = srv.LoadState() if err != nil { return err } // Create netACL err = srv.nacl.AddDisabledAddresses(srv.conf.Networks.Disabled...) if err != nil { return err } srv.nacl = netacl.NewNetACL() err = srv.nacl.AddEnabledAddresses(srv.conf.Networks.Enabled...) if err != nil { return err } naclYAML, err := yaml.Marshal(srv.nacl) if err != nil { return err } srv.log.Infof("Network ACL is:\n%s\n", string(naclYAML)) // Create database srv.db, err = maindb.NewDatabase(srv.conf.DataDir) err = srv.db.OpenDatabase() if err != nil { return err } // Load state err = srv.LoadState() if err != nil { return err } // Create operator operConfig := &operator.OperatorConfig{ Database: srv.db, } srv.oper, err = operator.NewOperator(operConfig) if err != nil { return err } if !srv.state.DatabaseInitialized { // Create schema srv.log.Infof("Init database") err = srv.db.InitDatabase() if err != nil { return err } srv.state.DatabaseInitialized = true err = srv.SaveState() if err != nil { return err } } // Create handler handlerConfig := &handler.HandlerConfig{ Operator: srv.oper, } srv.hand = handler.NewHandler(handlerConfig) if err != nil { return err } // Create service serviceConfig := &service.ServiceConfig{ Portnum: srv.conf.Service.Portnum, Address: srv.conf.Service.Address, Protocol: srv.conf.Service.Protocol, Hostname: srv.conf.Hostname, Handler: srv.hand, Operator: srv.oper, X509Cert: srv.x509cert, X509Key: srv.x509key, NetACL: srv.nacl, } srv.svc = service.NewService(serviceConfig) if err != nil { return err } return err } func (srv *Server) Run() error { var err error // Log configuration yamlConfig, err := srv.conf.String() 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) done := make(chan error, 1) // Run service startService := func(svc *service.Service, done chan error) { err = svc.Run() if err != nil { srv.log.Errorf("Service error: %v", err) done <- err } } go startService(srv.svc, done) 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.svc.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 }