/* * Copyright 2026 Oleg Borodin * * */ package server import ( "context" "fmt" "io/ioutil" "net" "os" "os/signal" "os/user" "path/filepath" "strconv" "sync" "syscall" "time" "mstore/app/config" "mstore/app/handler" "mstore/app/logger" "mstore/app/maindb" "mstore/app/service" "mstore/app/storage" "mstore/pkg/auxtool" "mstore/pkg/descr" "mstore/app/accoper" "mstore/app/fileoper" "mstore/app/imageoper" yaml "go.yaml.in/yaml/v4" ) type Server struct { conf *config.Config fiop *fileoper.Operator acop *accoper.Operator imop *imageoper.Operator svc *service.Service mdb *maindb.Database hand *handler.Handler logg *logger.Logger stor *storage.Storage stat descr.Server ctx context.Context cancel context.CancelFunc wg sync.WaitGroup logf *os.File //x509cert []byte //x509key []byte listen net.Listener } func NewServer() (*Server, error) { var err error srv := &Server{} srv.logg = logger.NewLoggerWithSubject("server") srv.ctx, srv.cancel = context.WithCancel(context.Background()) return srv, err } func (srv *Server) Handler() *handler.Handler { return srv.hand } func (srv *Server) Service() *service.Service { return srv.svc } func (srv *Server) SetLogdir(dir string) { srv.conf.Logpath = dir } func (srv *Server) SetRundir(dir string) { srv.conf.Runpath = dir } func (srv *Server) SetDatadir(dir string) { srv.conf.Database.Basepath = dir srv.conf.Storage.Basepath = dir srv.conf.Datadir = dir } func (srv *Server) SetPort(port uint32) { srv.conf.Service.Port = port } func (srv *Server) SetAsDaemon(asDaemon bool) { srv.conf.AsDaemon = asDaemon } func (srv *Server) Configure() error { var err error //srv.logg.Infof("Configuration server") srv.conf = config.NewConfig() if err != nil { return err } err = srv.conf.ReadConfigfile() if err != nil { srv.logg.Warningf("Error loading config file: %v", err) err = nil } err = srv.conf.ReadX509Cert() if err != nil { return err } return err } func (srv *Server) WriteStat() error { // Write status file var err error statefilePath := filepath.Join(srv.conf.Datadir, "server.yaml") stateData, err := yaml.Marshal(srv.stat) if err != nil { return err } err = ioutil.WriteFile(statefilePath, stateData, 0640) if err != nil { return err } return err } func (srv *Server) ReadStat() error { var err error // Read state file statefilePath := filepath.Join(srv.conf.Datadir, "server.yaml") if auxtool.FileExists(statefilePath) { stateData, err := ioutil.ReadFile(statefilePath) if err == nil { stat := descr.Server{} err = yaml.Unmarshal(stateData, &stat) if err != nil { return err } srv.stat = stat } } return err } func (srv *Server) Build() error { var err error srv.logg.Infof("Server building") confDump := srv.conf.String() srv.logg.Infof("Current server configuration is:\n%s\n", confDump) usr, err := user.Lookup(srv.conf.RunUser) if err != nil { return err } uid64, err := strconv.ParseInt(usr.Uid, 10, 64) if err != nil { return err } gid64, err := strconv.ParseInt(usr.Gid, 10, 64) if err != nil { return err } uid := int(uid64) gid := int(gid64) if srv.conf.AsDaemon { logdir := filepath.Dir(srv.conf.Logpath) srv.logg.Infof("Creating log directory %s", logdir) err = os.MkdirAll(logdir, 0750) if err != nil { return err } err = os.Chown(logdir, uid, gid) if err != nil { return err } rundir := filepath.Dir(srv.conf.Runpath) srv.logg.Infof("Creating run directory %s", rundir) err = os.MkdirAll(rundir, 0750) if err != nil { return err } err = os.Chown(rundir, uid, gid) if err != nil { return err } } // Creating datadir datadir := srv.conf.Datadir if !auxtool.DirExists(datadir) { // TODO: check access to dir srv.logg.Infof("Creating data directory %s ", datadir) err = os.MkdirAll(datadir, 0750) if err != nil { return err } } err = os.Chown(datadir, uid, gid) if err != nil { return err } // Read state file srv.logg.Infof("Reading server status") err = srv.ReadStat() if err != nil { return err } // Creating database dir dbdir := srv.conf.Database.Basepath srv.logg.Infof("Creating database directory %s ", dbdir) err = os.MkdirAll(dbdir, 0750) if err != nil { return err } // Creating storage dir srv.logg.Infof("Creating storage directory") datadir = srv.conf.Database.Basepath err = os.MkdirAll(datadir, 0750) if err != nil { return err } err = os.Chown(datadir, uid, gid) if err != nil { return err } cert, key := []byte(srv.conf.X509Cert), []byte(srv.conf.X509Key) addrinfo := fmt.Sprintf("%s:%d", srv.conf.Service.Address, srv.conf.Service.Port) listener, err := CreateTLSListener(addrinfo, cert, key) if err != nil { return err } srv.listen = listener // Change effective user err = syscall.Setuid(uid) if err != nil { return err } uidstr := strconv.FormatInt(int64(syscall.Geteuid()), 10) usr, err = user.LookupId(uidstr) if err != nil { return err } srv.logg.Warningf("Now run as user: %s", usr.Username) // Creating database mdb := maindb.NewDatabase(dbdir) srv.logg.Infof("Opening main database") err = mdb.OpenDatabase() if err != nil { return err } srv.mdb = mdb ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) // Created db scheme if !srv.stat.SchemeCreated { srv.logg.Infof("Initialize main database") err = mdb.InitDatabase() if err != nil { return err } srv.stat.SchemeCreated = true srv.stat.SchemeCreatedAt = auxtool.TimeNow() srv.logg.Infof("Writing server status") err = srv.WriteStat() if err != nil { return err } } // Created anonymous user if !srv.stat.AnonymousCreated { srv.logg.Infof("Creating anonymous user") err = mdb.WriteAnonymous(ctx) if err != nil { return err } srv.stat.AnonymousCreated = true srv.stat.AnonymousCreatedAt = auxtool.TimeNow() srv.logg.Infof("Writing server status") err = srv.WriteStat() if err != nil { return err } } if !srv.stat.InituserCreated { srv.logg.Infof("Creating init user") err = srv.mdb.WriteInituser(ctx) if err != nil { return err } srv.stat.InituserCreatedAt = auxtool.TimeNow() srv.stat.InituserCreated = true srv.logg.Infof("Writing server status") err = srv.WriteStat() if err != nil { return err } } // Creating storage srv.logg.Infof("Creating storage") store := storage.NewStorage(datadir) srv.stor = store // Creating account operator srv.logg.Infof("Creating account operator") accoperParams := &accoper.OperatorParams{ MainDB: srv.mdb, Store: srv.stor, } srv.acop, err = accoper.NewOperator(accoperParams) if err != nil { return err } // Creating file operator srv.logg.Infof("Creating file operator") fileoperParams := &fileoper.OperatorParams{ MainDB: srv.mdb, Store: srv.stor, Hostname: srv.conf.Hostname, } srv.fiop, err = fileoper.NewOperator(fileoperParams) if err != nil { return err } // Creating image operator srv.logg.Infof("Creating operator") imageoperParams := &imageoper.OperatorParams{ MainDB: srv.mdb, Store: srv.stor, } srv.imop, err = imageoper.NewOperator(imageoperParams) if err != nil { return err } // Creating handler srv.logg.Infof("Creating handler") handlerParams := &handler.HandlerParams{ MainDB: srv.mdb, FileOper: srv.fiop, AccOper: srv.acop, ImageOper: srv.imop, } srv.hand, err = handler.NewHandler(handlerParams) if err != nil { return err } // Creating service serviceParams := &service.ServiceParams{ Handler: srv.hand, Listener: srv.listen, } srv.logg.Infof("Creating service") srv.svc, err = service.NewService(serviceParams) if err != nil { return err } // Building service err = srv.svc.Build() if err != nil { return err } return err } func (srv *Server) Run() error { var err error if srv.conf.AsDaemon { // 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 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 } srv.logf = logFile // Write process ID 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 } // Start log rotator srv.Rotator() } currUser, err := user.Current() if err != nil { return err } srv.logg.Infof("Server started with user: %s", currUser.Username) uidstr := strconv.FormatInt(int64(syscall.Geteuid()), 10) usr, err := user.LookupId(uidstr) if err != nil { return err } srv.logg.Infof("Server run with user: %s", usr.Username) svcDone := make(chan error, 1) // Service run srv.logg.Infof("Start service") startService := func(svc *service.Service, done chan error) { err = svc.Run() if err != nil { srv.logg.Errorf("Service error: %v", err) done <- err } } go startService(srv.svc, svcDone) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) var signal os.Signal select { case signal = <-sigs: srv.logg.Infof("Services stopped by signal: %v", signal) srv.cancel() srv.svc.Stop() srv.wg.Wait() case err = <-svcDone: srv.logg.Infof("Service stopped by service error: %v", err) srv.cancel() srv.svc.Stop() srv.wg.Wait() } return err } func (srv *Server) PseudoFork() error { const successExit int = 0 var keyEnv string = "IMX0LTSELMRF8K" 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.AsDaemon { // Restart process process err = srv.PseudoFork() if err != nil { return err } } return err } func (srv *Server) Rotator() { srv.wg.Add(1) var counter uint64 logFunc := func() { for { counter += 1 select { case <-srv.ctx.Done(): srv.wg.Done() srv.logg.Infof("Log file rotator done") return default: } if (counter % 60) == 1 { stat, err := srv.logf.Stat() if err == nil && stat.Size() > srv.conf.LogLimit { srv.logg.Infof("Rotate log file") countFiles := 3 for i := 1; i < countFiles; i++ { nextName := fmt.Sprintf("%s.%d", srv.conf.Logpath, i+1) prevName := fmt.Sprintf("%s.%d", srv.conf.Logpath, i) os.Rename(prevName, nextName) } os.Rename(srv.conf.Logpath, srv.conf.Logpath+".1") logFile, err := os.OpenFile(srv.conf.Logpath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640) if err == nil { syscall.Dup2(int(logFile.Fd()), int(os.Stdout.Fd())) syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd())) srv.logf.Close() srv.logf = logFile } } } time.Sleep(1 * time.Second) } } go logFunc() }