193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
// Sshwifty - A Web SSH client
|
|
//
|
|
// Copyright (C) 2019-2022 Ni Rui <ranqus@gmail.com>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package application
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
goLog "log"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/nirui/sshwifty/application/command"
|
|
"github.com/nirui/sshwifty/application/configuration"
|
|
"github.com/nirui/sshwifty/application/log"
|
|
"github.com/nirui/sshwifty/application/server"
|
|
)
|
|
|
|
// ProccessSignaller send signal to the running application
|
|
type ProccessSignaller chan os.Signal
|
|
|
|
// ProccessSignallerBuilder builds a ProccessSignaler
|
|
type ProccessSignallerBuilder func() chan os.Signal
|
|
|
|
// DefaultProccessSignallerBuilder the default ProccessSignallerBuilder
|
|
func DefaultProccessSignallerBuilder() chan os.Signal {
|
|
return make(chan os.Signal, 1)
|
|
}
|
|
|
|
var (
|
|
screenLineWipper = []byte("\r")
|
|
)
|
|
|
|
// Application contains data required for the application, and yes I don't like
|
|
// to write comments
|
|
type Application struct {
|
|
screen io.Writer
|
|
logger log.Logger
|
|
}
|
|
|
|
// New creates a new Application
|
|
func New(screen io.Writer, logger log.Logger) Application {
|
|
return Application{
|
|
screen: screen,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Run execute the application. It will return when the application is finished
|
|
// running
|
|
func (a Application) run(
|
|
cLoader configuration.Loader,
|
|
closeSigBuilder ProccessSignallerBuilder,
|
|
commands command.Commands,
|
|
handlerBuilder server.HandlerBuilderBuilder,
|
|
) (bool, error) {
|
|
var err error
|
|
|
|
loaderName, c, cErr := cLoader(a.logger.Context("Configuration"))
|
|
|
|
if cErr != nil {
|
|
a.logger.Error("\"%s\" loader cannot load configuration: %s",
|
|
loaderName, cErr)
|
|
|
|
return false, cErr
|
|
}
|
|
|
|
// Allowing command to alter presets
|
|
c.Presets, err = commands.Reconfigure(c.Presets)
|
|
|
|
if err != nil {
|
|
a.logger.Error("Unable to reconfigure presets: %s", err)
|
|
|
|
return false, err
|
|
}
|
|
|
|
// Verify all configuration
|
|
err = c.Verify()
|
|
|
|
if err != nil {
|
|
a.logger.Error("Configuration was invalid: %s", err)
|
|
|
|
return false, err
|
|
}
|
|
|
|
closeNotify := closeSigBuilder()
|
|
closeNotifyDisableLock := sync.Mutex{}
|
|
signal.Notify(closeNotify, os.Kill, os.Interrupt, syscall.SIGHUP)
|
|
defer func() {
|
|
closeNotifyDisableLock.Lock()
|
|
defer closeNotifyDisableLock.Unlock()
|
|
if closeNotify == nil {
|
|
return
|
|
}
|
|
signal.Stop(closeNotify)
|
|
close(closeNotify)
|
|
closeNotify = nil
|
|
}()
|
|
|
|
servers := make([]*server.Serving, 0, len(c.Servers))
|
|
s := server.New(a.logger)
|
|
|
|
defer func() {
|
|
for i := len(servers); i > 0; i-- {
|
|
servers[i-1].Close()
|
|
}
|
|
s.Wait()
|
|
}()
|
|
|
|
for _, ss := range c.Servers {
|
|
newServer := s.Serve(c.Common(), ss, func(e error) {
|
|
closeNotifyDisableLock.Lock()
|
|
defer closeNotifyDisableLock.Unlock()
|
|
if closeNotify == nil {
|
|
return
|
|
}
|
|
err = e
|
|
signal.Stop(closeNotify)
|
|
close(closeNotify)
|
|
closeNotify = nil
|
|
}, handlerBuilder(commands))
|
|
servers = append(servers, newServer)
|
|
}
|
|
|
|
switch <-closeNotify {
|
|
case syscall.SIGHUP:
|
|
return true, nil
|
|
case syscall.SIGTERM:
|
|
fallthrough
|
|
case os.Kill:
|
|
fallthrough
|
|
case os.Interrupt:
|
|
a.screen.Write(screenLineWipper)
|
|
return false, nil
|
|
default:
|
|
closeNotifyDisableLock.Lock()
|
|
defer closeNotifyDisableLock.Unlock()
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
// Run execute the application. It will return when the application is finished
|
|
// running
|
|
func (a Application) Run(
|
|
cLoader configuration.Loader,
|
|
closeSigBuilder ProccessSignallerBuilder,
|
|
commands command.Commands,
|
|
handlerBuilder server.HandlerBuilderBuilder,
|
|
) error {
|
|
fmt.Fprintf(a.screen, banner, FullName, version, Author, URL)
|
|
|
|
goLog.SetOutput(a.logger)
|
|
defer goLog.SetOutput(os.Stderr)
|
|
|
|
a.logger.Info("Initializing")
|
|
defer a.logger.Info("Closed")
|
|
|
|
for {
|
|
restart, runErr := a.run(
|
|
cLoader, closeSigBuilder, commands, handlerBuilder)
|
|
|
|
if runErr != nil {
|
|
a.logger.Error("Unable to start due to error: %s", runErr)
|
|
|
|
return runErr
|
|
}
|
|
|
|
if restart {
|
|
a.logger.Info("Restarting")
|
|
|
|
continue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|