2021-03-31 07:23:48 +02:00
package main
import (
2022-01-12 11:13:25 +01:00
"context"
"crypto/tls"
2021-10-26 10:24:43 +02:00
"encoding/json"
2022-01-12 11:13:25 +01:00
"errors"
2021-10-11 11:11:14 +02:00
"flag"
2021-12-08 15:50:03 +01:00
"fmt"
2022-01-31 15:14:37 +01:00
"io"
2022-01-12 11:13:25 +01:00
"net"
2021-03-31 07:23:48 +02:00
"net/http"
2022-02-09 15:03:12 +01:00
"net/url"
2021-03-31 07:23:48 +02:00
"os"
2022-01-12 11:13:25 +01:00
"os/signal"
2022-03-24 10:35:52 +01:00
"runtime/debug"
2022-01-12 11:13:25 +01:00
"strings"
"sync"
"syscall"
"time"
2021-03-31 07:23:48 +02:00
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
2022-01-27 09:40:59 +01:00
"github.com/ClusterCockpit/cc-backend/api"
"github.com/ClusterCockpit/cc-backend/auth"
"github.com/ClusterCockpit/cc-backend/config"
"github.com/ClusterCockpit/cc-backend/graph"
"github.com/ClusterCockpit/cc-backend/graph/generated"
2022-01-27 10:35:26 +01:00
"github.com/ClusterCockpit/cc-backend/log"
2022-01-27 09:40:59 +01:00
"github.com/ClusterCockpit/cc-backend/metricdata"
2022-02-07 07:09:47 +01:00
"github.com/ClusterCockpit/cc-backend/repository"
2022-01-27 09:40:59 +01:00
"github.com/ClusterCockpit/cc-backend/templates"
2022-03-14 08:45:17 +01:00
"github.com/google/gops/agent"
2021-03-31 07:23:48 +02:00
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
2022-01-20 10:00:55 +01:00
_ "github.com/go-sql-driver/mysql"
2021-03-31 07:23:48 +02:00
_ "github.com/mattn/go-sqlite3"
)
2022-02-10 18:48:58 +01:00
var jobRepo * repository . JobRepository
2021-11-26 10:34:29 +01:00
2021-12-16 09:35:03 +01:00
// Format of the configurartion (file). See below for the defaults.
2021-12-08 10:15:25 +01:00
type ProgramConfig struct {
2021-12-16 09:35:03 +01:00
// Address where the http (or https) server will listen on (for example: 'localhost:80').
Addr string ` json:"addr" `
2022-01-12 11:13:25 +01:00
// Drop root permissions once .env was read and the port was taken.
User string ` json:"user" `
Group string ` json:"group" `
2021-12-16 09:35:03 +01:00
// Disable authentication (for everything: API, Web-UI, ...)
DisableAuthentication bool ` json:"disable-authentication" `
// Folder where static assets can be found, will be served directly
StaticFiles string ` json:"static-files" `
2022-01-20 10:00:55 +01:00
// 'sqlite3' or 'mysql' (mysql will work for mariadb as well)
DBDriver string ` json:"db-driver" `
// For sqlite3 a filename, for mysql a DSN in this format: https://github.com/go-sql-driver/mysql#dsn-data-source-name (Without query parameters!).
2021-12-16 09:35:03 +01:00
DB string ` json:"db" `
// Path to the job-archive
JobArchive string ` json:"job-archive" `
// Keep all metric data in the metric data repositories,
// do not write to the job-archive.
DisableArchive bool ` json:"disable-archive" `
// For LDAP Authentication and user syncronisation.
LdapConfig * auth . LdapConfig ` json:"ldap" `
2022-02-16 11:50:25 +01:00
// Specifies for how long a session or JWT shall be valid
// as a string parsable by time.ParseDuration().
// If 0 or empty, the session/token does not expire!
SessionMaxAge string ` json:"session-max-age" `
JwtMaxAge string ` json:"jwt-max-age" `
2021-12-16 09:35:03 +01:00
// If both those options are not empty, use HTTPS using those certificates.
HttpsCertFile string ` json:"https-cert-file" `
HttpsKeyFile string ` json:"https-key-file" `
2022-03-14 08:45:17 +01:00
// If not the empty string and `addr` does not end in ":80",
// redirect every request incoming at port 80 to that url.
RedirectHttpTo string ` json:"redirect-http-to" `
2021-12-16 09:35:03 +01:00
// If overwriten, at least all the options in the defaults below must
// be provided! Most options here can be overwritten by the user.
UiDefaults map [ string ] interface { } ` json:"ui-defaults" `
2022-01-07 09:39:00 +01:00
// Where to store MachineState files
MachineStateDir string ` json:"machine-state-dir" `
2021-12-08 10:15:25 +01:00
}
2021-03-31 07:23:48 +02:00
2021-12-08 10:15:25 +01:00
var programConfig ProgramConfig = ProgramConfig {
2022-01-12 11:13:25 +01:00
Addr : ":8080" ,
2021-12-08 10:15:25 +01:00
DisableAuthentication : false ,
StaticFiles : "./frontend/public" ,
2022-01-20 10:00:55 +01:00
DBDriver : "sqlite3" ,
2021-12-08 10:15:25 +01:00
DB : "./var/job.db" ,
JobArchive : "./var/job-archive" ,
2021-12-16 09:35:03 +01:00
DisableArchive : false ,
2022-02-15 10:03:09 +01:00
LdapConfig : nil ,
2022-02-16 11:50:25 +01:00
SessionMaxAge : "168h" ,
JwtMaxAge : "0" ,
2021-12-08 10:15:25 +01:00
UiDefaults : map [ string ] interface { } {
"analysis_view_histogramMetrics" : [ ] string { "flops_any" , "mem_bw" , "mem_used" } ,
"analysis_view_scatterPlotMetrics" : [ ] [ ] string { { "flops_any" , "mem_bw" } , { "flops_any" , "cpu_load" } , { "cpu_load" , "mem_bw" } } ,
"job_view_nodestats_selectedMetrics" : [ ] string { "flops_any" , "mem_bw" , "mem_used" } ,
"job_view_polarPlotMetrics" : [ ] string { "flops_any" , "mem_bw" , "mem_used" , "net_bw" , "file_bw" } ,
"job_view_selectedMetrics" : [ ] string { "flops_any" , "mem_bw" , "mem_used" } ,
"plot_general_colorBackground" : true ,
"plot_general_colorscheme" : [ ] string { "#00bfff" , "#0000ff" , "#ff00ff" , "#ff0000" , "#ff8000" , "#ffff00" , "#80ff00" } ,
2022-03-16 10:09:00 +01:00
"plot_general_lineWidth" : 3 ,
2022-03-02 10:48:52 +01:00
"plot_list_hideShortRunningJobs" : 5 * 60 ,
2021-12-08 10:15:25 +01:00
"plot_list_jobsPerPage" : 10 ,
2022-03-03 15:33:51 +01:00
"plot_list_selectedMetrics" : [ ] string { "cpu_load" , "ipc" , "mem_used" , "flops_any" , "mem_bw" } ,
"plot_view_plotsPerRow" : 3 ,
2021-12-08 10:15:25 +01:00
"plot_view_showPolarplot" : true ,
"plot_view_showRoofline" : true ,
"plot_view_showStatTable" : true ,
2022-02-02 13:59:08 +01:00
"system_view_selectedMetric" : "cpu_load" ,
2021-12-08 10:15:25 +01:00
} ,
}
2021-10-26 10:24:43 +02:00
2021-12-08 10:15:25 +01:00
func main ( ) {
2022-03-14 08:45:17 +01:00
var flagReinitDB , flagStopImmediately , flagSyncLDAP , flagGops bool
2022-02-24 11:54:36 +01:00
var flagConfigFile , flagImportJob string
2022-01-10 16:14:54 +01:00
var flagNewUser , flagDelUser , flagGenJWT string
2022-03-15 08:29:29 +01:00
flag . BoolVar ( & flagReinitDB , "init-db" , false , "Go through job-archive and re-initialize the 'job', 'tag', and 'jobtag' tables (all running jobs will be lost!)" )
flag . BoolVar ( & flagSyncLDAP , "sync-ldap" , false , "Sync the 'user' table with ldap" )
2021-12-08 10:15:25 +01:00
flag . BoolVar ( & flagStopImmediately , "no-server" , false , "Do not start a server, stop right after initialization and argument handling" )
2022-03-15 08:29:29 +01:00
flag . BoolVar ( & flagGops , "gops" , false , "Listen via github.com/google/gops/agent (for debugging)" )
flag . StringVar ( & flagConfigFile , "config" , "" , "Overwrite the global config options by those specified in `config.json`" )
2022-01-27 09:29:11 +01:00
flag . StringVar ( & flagNewUser , "add-user" , "" , "Add a new user. Argument format: `<username>:[admin,api,user]:<password>`" )
2022-03-15 08:29:29 +01:00
flag . StringVar ( & flagDelUser , "del-user" , "" , "Remove user by `username`" )
flag . StringVar ( & flagGenJWT , "jwt" , "" , "Generate and print a JWT for the user specified by its `username`" )
2022-02-24 11:54:36 +01:00
flag . StringVar ( & flagImportJob , "import-job" , "" , "Import a job. Argument format: `<path-to-meta.json>:<path-to-data.json>,...`" )
2021-12-08 10:15:25 +01:00
flag . Parse ( )
2021-03-31 07:23:48 +02:00
2022-03-15 08:29:29 +01:00
// See https://github.com/google/gops (Runtime overhead is almost zero)
2022-03-14 08:45:17 +01:00
if flagGops {
if err := agent . Listen ( agent . Options { } ) ; err != nil {
log . Fatalf ( "gops/agent.Listen failed: %s" , err . Error ( ) )
}
}
2022-01-12 11:13:25 +01:00
if err := loadEnv ( "./.env" ) ; err != nil && ! os . IsNotExist ( err ) {
log . Fatalf ( "parsing './.env' file failed: %s" , err . Error ( ) )
}
2021-12-08 10:15:25 +01:00
if flagConfigFile != "" {
2022-02-24 11:54:36 +01:00
f , err := os . Open ( flagConfigFile )
2021-12-08 10:15:25 +01:00
if err != nil {
log . Fatal ( err )
}
2022-02-24 11:54:36 +01:00
defer f . Close ( )
dec := json . NewDecoder ( f )
dec . DisallowUnknownFields ( )
if err := dec . Decode ( & programConfig ) ; err != nil {
2021-10-11 11:11:14 +02:00
log . Fatal ( err )
}
2021-10-26 10:24:43 +02:00
}
2021-10-11 11:11:14 +02:00
2022-03-15 08:29:29 +01:00
// As a special case for `db`, allow using an environment variable instead of the value
// stored in the config. This can be done for people having security concerns about storing
// the password for their mysql database in the config.json.
2022-01-31 15:14:37 +01:00
if strings . HasPrefix ( programConfig . DB , "env:" ) {
envvar := strings . TrimPrefix ( programConfig . DB , "env:" )
programConfig . DB = os . Getenv ( envvar )
}
2021-12-08 10:15:25 +01:00
var err error
2022-03-15 08:29:29 +01:00
var db * sqlx . DB
2022-01-20 10:00:55 +01:00
if programConfig . DBDriver == "sqlite3" {
db , err = sqlx . Open ( "sqlite3" , fmt . Sprintf ( "%s?_foreign_keys=on" , programConfig . DB ) )
if err != nil {
log . Fatal ( err )
}
2022-03-15 08:29:29 +01:00
// sqlite does not multithread. Having more than one connection open would just mean
// waiting for locks.
2022-01-20 10:00:55 +01:00
db . SetMaxOpenConns ( 1 )
} else if programConfig . DBDriver == "mysql" {
db , err = sqlx . Open ( "mysql" , fmt . Sprintf ( "%s?multiStatements=true" , programConfig . DB ) )
if err != nil {
log . Fatal ( err )
}
2021-10-11 11:11:14 +02:00
2022-01-20 10:00:55 +01:00
db . SetConnMaxLifetime ( time . Minute * 3 )
db . SetMaxOpenConns ( 10 )
db . SetMaxIdleConns ( 10 )
} else {
log . Fatalf ( "unsupported database driver: %s" , programConfig . DBDriver )
}
2021-12-16 09:35:03 +01:00
2022-03-15 08:29:29 +01:00
// Initialize sub-modules and handle all command line flags.
// The order here is important! For example, the metricdata package
// depends on the config package.
2021-11-26 10:34:29 +01:00
2022-03-02 10:50:08 +01:00
var authentication * auth . Authentication
2021-12-08 10:15:25 +01:00
if ! programConfig . DisableAuthentication {
2022-03-02 10:50:08 +01:00
authentication = & auth . Authentication { }
2022-02-16 11:50:25 +01:00
if d , err := time . ParseDuration ( programConfig . SessionMaxAge ) ; err != nil {
authentication . SessionMaxAge = d
}
if d , err := time . ParseDuration ( programConfig . JwtMaxAge ) ; err != nil {
authentication . JwtMaxAge = d
}
2022-02-14 14:22:44 +01:00
if err := authentication . Init ( db , programConfig . LdapConfig ) ; err != nil {
2021-12-08 10:15:25 +01:00
log . Fatal ( err )
}
2021-03-31 07:23:48 +02:00
2021-12-08 10:15:25 +01:00
if flagNewUser != "" {
2022-02-14 14:22:44 +01:00
if err := authentication . AddUser ( flagNewUser ) ; err != nil {
2021-12-08 10:15:25 +01:00
log . Fatal ( err )
}
}
if flagDelUser != "" {
2022-02-14 14:22:44 +01:00
if err := authentication . DelUser ( flagDelUser ) ; err != nil {
2021-12-08 10:15:25 +01:00
log . Fatal ( err )
}
}
2021-11-26 10:34:29 +01:00
2021-12-08 10:15:25 +01:00
if flagSyncLDAP {
2022-02-14 14:22:44 +01:00
if err := authentication . SyncWithLDAP ( true ) ; err != nil {
log . Fatal ( err )
}
2021-12-08 10:15:25 +01:00
}
2022-01-10 16:14:54 +01:00
if flagGenJWT != "" {
2022-02-14 14:22:44 +01:00
user , err := authentication . FetchUser ( flagGenJWT )
2022-01-10 16:14:54 +01:00
if err != nil {
log . Fatal ( err )
}
2022-01-27 09:29:11 +01:00
if ! user . HasRole ( auth . RoleApi ) {
2022-01-27 10:35:26 +01:00
log . Warn ( "that user does not have the API role" )
2022-01-10 16:14:54 +01:00
}
2022-02-14 14:22:44 +01:00
jwt , err := authentication . ProvideJWT ( user )
2022-01-10 16:14:54 +01:00
if err != nil {
log . Fatal ( err )
}
fmt . Printf ( "JWT for '%s': %s\n" , user . Username , jwt )
}
2021-12-08 10:15:25 +01:00
} else if flagNewUser != "" || flagDelUser != "" {
2022-01-27 10:35:26 +01:00
log . Fatal ( "arguments --add-user and --del-user can only be used if authentication is enabled" )
2021-09-21 16:06:41 +02:00
}
2021-12-08 10:15:25 +01:00
if err := config . Init ( db , ! programConfig . DisableAuthentication , programConfig . UiDefaults , programConfig . JobArchive ) ; err != nil {
log . Fatal ( err )
}
2021-10-26 10:24:43 +02:00
2021-12-16 09:35:03 +01:00
if err := metricdata . Init ( programConfig . JobArchive , programConfig . DisableArchive ) ; err != nil {
2021-12-08 10:15:25 +01:00
log . Fatal ( err )
2021-10-26 10:24:43 +02:00
}
2021-12-08 10:15:25 +01:00
if flagReinitDB {
2022-03-15 08:29:29 +01:00
if err := repository . InitDB ( db , programConfig . JobArchive ) ; err != nil {
2021-12-08 10:15:25 +01:00
log . Fatal ( err )
2021-10-26 10:24:43 +02:00
}
2021-12-08 10:15:25 +01:00
}
2021-10-26 10:24:43 +02:00
2022-02-24 11:54:36 +01:00
jobRepo = & repository . JobRepository { DB : db }
if err := jobRepo . Init ( ) ; err != nil {
log . Fatal ( err )
}
if flagImportJob != "" {
if err := jobRepo . HandleImportFlag ( flagImportJob ) ; err != nil {
log . Fatalf ( "import failed: %s" , err . Error ( ) )
}
}
2021-12-08 10:15:25 +01:00
if flagStopImmediately {
return
}
2021-10-26 10:24:43 +02:00
2022-03-15 08:29:29 +01:00
// Setup the http.Handler/Router used by the server
2021-10-26 10:24:43 +02:00
2022-02-17 09:04:57 +01:00
resolver := & graph . Resolver { DB : db , Repo : jobRepo }
2021-12-08 15:50:03 +01:00
graphQLEndpoint := handler . NewDefaultServer ( generated . NewExecutableSchema ( generated . Config { Resolvers : resolver } ) )
2022-01-20 10:00:55 +01:00
if os . Getenv ( "DEBUG" ) != "1" {
2022-03-15 08:29:29 +01:00
// Having this handler means that a error message is returned via GraphQL instead of the connection simply beeing closed.
// The problem with this is that then, no more stacktrace is printed to stderr.
2022-01-20 10:00:55 +01:00
graphQLEndpoint . SetRecoverFunc ( func ( ctx context . Context , err interface { } ) error {
switch e := err . ( type ) {
case string :
return fmt . Errorf ( "panic: %s" , e )
case error :
return fmt . Errorf ( "panic caused by: %w" , e )
}
2022-01-10 16:14:54 +01:00
2022-01-20 10:00:55 +01:00
return errors . New ( "internal server error (panic)" )
} )
}
2022-01-10 16:14:54 +01:00
2021-12-17 15:49:22 +01:00
api := & api . RestApi {
2022-02-10 18:48:58 +01:00
JobRepository : jobRepo ,
2022-01-07 09:39:00 +01:00
Resolver : resolver ,
MachineStateDir : programConfig . MachineStateDir ,
2022-03-02 10:50:08 +01:00
Authentication : authentication ,
2021-12-16 09:35:03 +01:00
}
2021-12-08 10:15:25 +01:00
r := mux . NewRouter ( )
2022-03-15 08:29:29 +01:00
r . HandleFunc ( "/login" , func ( rw http . ResponseWriter , r * http . Request ) {
templates . Render ( rw , r , "login.tmpl" , & templates . Page { Title : "Login" } )
} ) . Methods ( http . MethodGet )
2022-02-03 09:39:04 +01:00
r . HandleFunc ( "/imprint" , func ( rw http . ResponseWriter , r * http . Request ) {
2022-03-15 08:29:29 +01:00
templates . Render ( rw , r , "imprint.tmpl" , & templates . Page { Title : "Imprint" } )
2022-02-03 09:39:04 +01:00
} )
r . HandleFunc ( "/privacy" , func ( rw http . ResponseWriter , r * http . Request ) {
2022-03-15 08:29:29 +01:00
templates . Render ( rw , r , "privacy.tmpl" , & templates . Page { Title : "Privacy" } )
2022-02-03 09:39:04 +01:00
} )
2021-12-08 10:15:25 +01:00
2022-03-15 08:29:29 +01:00
// Some routes, such as /login or /query, should only be accessible to a user that is logged in.
// Those should be mounted to this subrouter. If authentication is enabled, a middleware will prevent
// any unauthenticated accesses.
2021-12-08 10:15:25 +01:00
secured := r . PathPrefix ( "/" ) . Subrouter ( )
if ! programConfig . DisableAuthentication {
2022-02-14 14:22:44 +01:00
r . Handle ( "/login" , authentication . Login (
// On success:
http . RedirectHandler ( "/" , http . StatusTemporaryRedirect ) ,
// On failure:
2022-02-16 09:01:47 +01:00
func ( rw http . ResponseWriter , r * http . Request , err error ) {
2022-02-14 14:22:44 +01:00
rw . WriteHeader ( http . StatusUnauthorized )
templates . Render ( rw , r , "login.tmpl" , & templates . Page {
Title : "Login failed - ClusterCockpit" ,
2022-02-16 09:01:47 +01:00
Error : err . Error ( ) ,
2022-02-14 14:22:44 +01:00
} )
} ) ) . Methods ( http . MethodPost )
2022-02-16 09:01:47 +01:00
r . Handle ( "/logout" , authentication . Logout ( http . HandlerFunc ( func ( rw http . ResponseWriter , r * http . Request ) {
rw . WriteHeader ( http . StatusOK )
templates . Render ( rw , r , "login.tmpl" , & templates . Page {
Title : "Bye - ClusterCockpit" ,
Info : "Logout sucessful" ,
} )
} ) ) ) . Methods ( http . MethodPost )
2022-02-14 14:22:44 +01:00
secured . Use ( func ( next http . Handler ) http . Handler {
return authentication . Auth (
// On success;
next ,
// On failure:
2022-02-16 09:01:47 +01:00
func ( rw http . ResponseWriter , r * http . Request , err error ) {
2022-02-14 14:22:44 +01:00
rw . WriteHeader ( http . StatusUnauthorized )
templates . Render ( rw , r , "login.tmpl" , & templates . Page {
Title : "Authentication failed - ClusterCockpit" ,
Error : err . Error ( ) ,
} )
} )
} )
2021-12-08 10:15:25 +01:00
}
2022-03-15 08:29:29 +01:00
r . Handle ( "/playground" , playground . Handler ( "GraphQL playground" , "/query" ) )
2021-12-08 10:15:25 +01:00
secured . Handle ( "/query" , graphQLEndpoint )
2021-12-16 09:35:03 +01:00
2022-03-15 08:29:29 +01:00
// Send a searchId and then reply with a redirect to a user or job.
2022-02-09 15:03:12 +01:00
secured . HandleFunc ( "/search" , func ( rw http . ResponseWriter , r * http . Request ) {
if search := r . URL . Query ( ) . Get ( "searchId" ) ; search != "" {
job , username , err := api . JobRepository . FindJobOrUser ( r . Context ( ) , search )
if err == repository . ErrNotFound {
http . Redirect ( rw , r , "/monitoring/jobs/?jobId=" + url . QueryEscape ( search ) , http . StatusTemporaryRedirect )
return
} else if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
return
}
if username != "" {
http . Redirect ( rw , r , "/monitoring/user/" + username , http . StatusTemporaryRedirect )
return
} else {
http . Redirect ( rw , r , fmt . Sprintf ( "/monitoring/job/%d" , job ) , http . StatusTemporaryRedirect )
return
}
} else {
http . Error ( rw , "'searchId' query parameter missing" , http . StatusBadRequest )
}
} )
2022-03-15 08:29:29 +01:00
// Mount all /monitoring/... and /api/... routes.
2022-02-03 09:39:04 +01:00
setupRoutes ( secured , routes )
2021-12-17 15:49:22 +01:00
api . MountRoutes ( secured )
2021-12-08 15:50:03 +01:00
2021-12-08 10:15:25 +01:00
r . PathPrefix ( "/" ) . Handler ( http . FileServer ( http . Dir ( programConfig . StaticFiles ) ) )
2022-01-31 15:14:37 +01:00
r . Use ( handlers . CompressHandler )
2022-02-16 12:52:45 +01:00
r . Use ( handlers . RecoveryHandler ( handlers . PrintRecoveryStack ( true ) ) )
2022-01-31 15:14:37 +01:00
r . Use ( handlers . CORS (
2021-12-08 10:15:25 +01:00
handlers . AllowedHeaders ( [ ] string { "X-Requested-With" , "Content-Type" , "Authorization" } ) ,
handlers . AllowedMethods ( [ ] string { "GET" , "POST" , "HEAD" , "OPTIONS" } ) ,
2022-01-31 15:14:37 +01:00
handlers . AllowedOrigins ( [ ] string { "*" } ) ) )
2022-03-15 08:29:29 +01:00
handler := handlers . CustomLoggingHandler ( io . Discard , r , func ( _ io . Writer , params handlers . LogFormatterParams ) {
if strings . HasPrefix ( params . Request . RequestURI , "/api/" ) {
log . Infof ( "%s %s (%d, %.02fkb, %dms)" ,
params . Request . Method , params . URL . RequestURI ( ) ,
params . StatusCode , float32 ( params . Size ) / 1024 ,
time . Since ( params . TimeStamp ) . Milliseconds ( ) )
} else {
log . Debugf ( "%s %s (%d, %.02fkb, %dms)" ,
params . Request . Method , params . URL . RequestURI ( ) ,
params . StatusCode , float32 ( params . Size ) / 1024 ,
time . Since ( params . TimeStamp ) . Milliseconds ( ) )
}
2022-01-31 15:14:37 +01:00
} )
2021-12-08 10:15:25 +01:00
2022-01-12 11:13:25 +01:00
var wg sync . WaitGroup
server := http . Server {
ReadTimeout : 10 * time . Second ,
WriteTimeout : 10 * time . Second ,
Handler : handler ,
Addr : programConfig . Addr ,
}
2021-12-08 10:15:25 +01:00
// Start http or https server
2022-01-12 11:13:25 +01:00
listener , err := net . Listen ( "tcp" , programConfig . Addr )
if err != nil {
log . Fatal ( err )
}
2022-03-14 08:45:17 +01:00
if ! strings . HasSuffix ( programConfig . Addr , ":80" ) && programConfig . RedirectHttpTo != "" {
go func ( ) {
http . ListenAndServe ( ":80" , http . RedirectHandler ( programConfig . RedirectHttpTo , http . StatusMovedPermanently ) )
} ( )
}
2021-12-08 10:15:25 +01:00
if programConfig . HttpsCertFile != "" && programConfig . HttpsKeyFile != "" {
2022-01-12 11:13:25 +01:00
cert , err := tls . LoadX509KeyPair ( programConfig . HttpsCertFile , programConfig . HttpsKeyFile )
if err != nil {
log . Fatal ( err )
}
listener = tls . NewListener ( listener , & tls . Config {
Certificates : [ ] tls . Certificate { cert } ,
} )
log . Printf ( "HTTPS server listening at %s..." , programConfig . Addr )
2021-12-08 10:15:25 +01:00
} else {
2022-01-12 11:13:25 +01:00
log . Printf ( "HTTP server listening at %s..." , programConfig . Addr )
}
// Because this program will want to bind to a privileged port (like 80), the listener must
// be established first, then the user can be changed, and after that,
// the actuall http server can be started.
if err := dropPrivileges ( ) ; err != nil {
log . Fatalf ( "error while changing user: %s" , err . Error ( ) )
2021-12-08 10:15:25 +01:00
}
2022-01-12 11:13:25 +01:00
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
if err := server . Serve ( listener ) ; err != nil && err != http . ErrServerClosed {
log . Fatal ( err )
}
} ( )
wg . Add ( 1 )
sigs := make ( chan os . Signal , 1 )
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )
go func ( ) {
defer wg . Done ( )
<- sigs
systemdNotifiy ( false , "shutting down" )
// First shut down the server gracefully (waiting for all ongoing requests)
server . Shutdown ( context . Background ( ) )
// Then, wait for any async archivings still pending...
api . OngoingArchivings . Wait ( )
} ( )
2022-03-24 10:35:52 +01:00
if os . Getenv ( "GOGC" ) == "" {
debug . SetGCPercent ( 25 )
}
2022-01-12 11:13:25 +01:00
systemdNotifiy ( true , "running" )
wg . Wait ( )
log . Print ( "Gracefull shutdown completed!" )
2021-10-26 10:24:43 +02:00
}