Skip to content

Commit

Permalink
Fixed embedded DB having multiple owners, option to open URL without …
Browse files Browse the repository at this point in the history
…starting server

In stand-alone mode, embedded DB is controlled via service management - no issue there. But in portable mode, multiple instances can be opened which all attempt to control the embedded DB. This was changed so that only the instance that can successfully start the embedded DB can control it; other instances shut down if they cannot get ownership.

As no clustering support exists yet, multiple instances working on the same DB is not valid in any case.

Also added option to use main executable to just open the application URL without starting the server.
  • Loading branch information
r3-gabriel committed Oct 4, 2021
1 parent f80b727 commit 23f2af8
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 31 deletions.
3 changes: 1 addition & 2 deletions db/embedded/embedded.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ func Start() error {
}

if state {
log.Info("server", "embedded database already running")
return nil
return fmt.Errorf("database already running, another instance is likely active")
}
_, err = execWaitFor(dbBinCtl, []string{"start", "-D", dbData,
fmt.Sprintf(`-o "-p %d"`, config.File.Db.Port)}, []string{msgStarted}, 10)
Expand Down
74 changes: 45 additions & 29 deletions r3.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type cliInput struct {
configFile string
dynamicPort bool
http bool
openStart bool
open bool
run bool
serviceName string
serviceStart bool
Expand All @@ -65,10 +65,11 @@ type cliInput struct {
setData string
}
type program struct {
cli cliInput
logger service.Logger // logs to the operating system if called as service, otherwise to stdOut
stopping bool
webServer *http.Server
cli cliInput
embeddedDbOwned bool // this instance has started the embedded database
logger service.Logger // logs to the operating system if called as service, otherwise to stdOut
stopping bool
webServer *http.Server
}

func main() {
Expand All @@ -84,10 +85,10 @@ func main() {
flag.BoolVar(&cli.serviceUninstall, "uninstall", false, fmt.Sprintf("Uninstall %s service", appName))
flag.BoolVar(&cli.serviceStart, "start", false, fmt.Sprintf("Start %s service", appName))
flag.BoolVar(&cli.serviceStop, "stop", false, fmt.Sprintf("Stop %s service", appName))
flag.BoolVar(&cli.openStart, "open", false, fmt.Sprintf("Open URL of %s in default browser (combined with -run)", appName))
flag.BoolVar(&cli.open, "open", false, fmt.Sprintf("Open URL of %s in default browser (combined with -run)", appName))
flag.BoolVar(&cli.dynamicPort, "dynamicport", false, "Start with a port provided by the operating system (combined with -run)")
flag.BoolVar(&cli.http, "http", false, "Start with unencrypted HTTP (for testing/development only, combined with -run)")
flag.BoolVar(&cli.run, "run", false, fmt.Sprintf("Run %s from within this console", appName))
flag.BoolVar(&cli.http, "http", false, "Start with HTTP (not encrypted, for testing/development only, combined with -run)")
flag.BoolVar(&cli.run, "run", false, fmt.Sprintf("Run %s from within this console (see 'config.json' for configuration)", appName))
flag.StringVar(&cli.adminCreate, "newadmin", "", "Create new admin user (username:password), password must not contain spaces or colons")
flag.StringVar(&cli.setData, "setdata", "", "Write to config file: Data directory (platform files and database if stand-alone)")
flag.StringVar(&cli.configFile, "config", "", "Start with alternative config file location (combined with -run)")
Expand Down Expand Up @@ -138,23 +139,20 @@ func main() {

// print usage info if interactive and no arguments were added
if service.Interactive() && len(os.Args) == 1 {
fmt.Printf("Available parameters:\n")
flag.PrintDefaults()

fmt.Printf("\n################################################################################\n")
fmt.Printf("This is the executable of %s, the open application platform, v%s\n", appName, appVersion)
fmt.Printf("Copyright (c) 2019-2021 Gabriel Victor Herbert\n\n")
fmt.Printf("%s can be installed as service (-install) or run from the console (-run).\n", appName)
fmt.Printf("For the first start, %s needs to have access to an empty PostgreSQL database\n", appName)
fmt.Printf("with full permissions; database connection details need to be stored in %s´s\n", appName)
fmt.Printf("configuration file, by default: 'config.json'.\n\n")
fmt.Printf("Windows only: If the stand-alone version was installed or the portable version\n")
fmt.Printf("is used, the system is already pre-configured.\n\n")
fmt.Printf("Please visit https://rei3.de/admindocu-en_us/ for more details.\n")
fmt.Printf("%s can be installed as service (-install) or run from the console (-run).\n\n", appName)
fmt.Printf("When %s is running, use any modern browser to access it (port 443 by default).\n\n", appName)
fmt.Printf("For installation instructions, please refer to the included README file or visit\n")
fmt.Printf("https://rei3.de/admindocu-en_us/ for the full admin documentation.\n")
fmt.Printf("################################################################################\n\n")
fmt.Printf("Available command line flags:\n")

flag.PrintDefaults()

// wait for user input to keep console open
fmt.Printf("\nPlease read above how to install or start %s. Press enter to return.\n", appName)
fmt.Printf("See above for available parameters. Press enter to return.\n")

reader := bufio.NewReader(os.Stdin)
reader.ReadString('\n')
Expand Down Expand Up @@ -222,6 +220,17 @@ func main() {
return
}

// main executable can be used to open the app in default browser even if its not started (-open without -run)
// used for shortcuts in start menu when installed on Windows systems with desktop experience
// if dynamic port is used, we cannot open app without starting it (port is not known)
if cli.open && !cli.dynamicPort {
protocol := "https"
if cli.http {
protocol = "http"
}
tools.OpenRessource(fmt.Sprintf("%s://localhost:%d", protocol, config.File.Web.Port), false)
}

// interactive, app only starts if to be run from console or when creating an admin user
if service.Interactive() && !cli.run && cli.adminCreate == "" {
return
Expand Down Expand Up @@ -273,6 +282,10 @@ func (prg *program) execute(svc service.Service) {
prg.logger.Errorf("failed to start embedded database, %v", err)
return
}

// we own the embedded DB if we can successfully start it
// otherwise another instance might be running it
prg.embeddedDbOwned = true
}

// connect to database
Expand Down Expand Up @@ -367,7 +380,7 @@ func (prg *program) execute(svc service.Service) {
return
}

// start main web server
// prepare web server
go websocket.StartBackgroundTasks()

mux := http.NewServeMux()
Expand Down Expand Up @@ -402,10 +415,17 @@ func (prg *program) execute(svc service.Service) {
}
log.Info("server", fmt.Sprintf("starting web handlers for '%s'", webServerString))

if prg.cli.http {
if prg.cli.openStart {
tools.OpenRessource(fmt.Sprintf("http://localhost:%d", config.File.Web.Port), false)
// if dynamic port is used we can only now open the app in default browser (port is now known)
if prg.cli.open && prg.cli.dynamicPort {
protocol := "https"
if prg.cli.http {
protocol = "http"
}
tools.OpenRessource(fmt.Sprintf("%s://localhost:%d", protocol, config.File.Web.Port), false)
}

// start web server and block routine
if prg.cli.http {
if err := prg.webServer.Serve(webListener); err != nil && err != http.ErrServerClosed {
prg.logger.Error(err)
}
Expand All @@ -417,10 +437,6 @@ func (prg *program) execute(svc service.Service) {
prg.logger.Error(err)
return
}

if prg.cli.openStart {
tools.OpenRessource(fmt.Sprintf("https://localhost:%d", config.File.Web.Port), false)
}
if err := prg.webServer.ServeTLS(webListener, certPath, keyPath); err != nil && err != http.ErrServerClosed {
prg.logger.Error(err)
}
Expand Down Expand Up @@ -456,8 +472,8 @@ func (prg *program) Stop(svc service.Service) error {
log.Info("server", "stopped database handler")
}

// stop embedded database if used
if config.File.Db.Embedded {
// stop embedded database if owned
if prg.embeddedDbOwned {
if err := embedded.Stop(); err != nil {
prg.logger.Error(err)
}
Expand Down

0 comments on commit 23f2af8

Please sign in to comment.