2022-07-29 06:29:21 +02:00
// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2021-03-31 07:23:48 +02:00
package main
import (
2022-01-12 11:13:25 +01:00
"context"
"crypto/tls"
"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"
"os"
2022-01-12 11:13:25 +01:00
"os/signal"
2022-05-09 11:53:41 +02:00
"runtime"
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-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/internal/api"
"github.com/ClusterCockpit/cc-backend/internal/auth"
"github.com/ClusterCockpit/cc-backend/internal/config"
"github.com/ClusterCockpit/cc-backend/internal/graph"
"github.com/ClusterCockpit/cc-backend/internal/graph/generated"
2023-04-28 08:49:58 +02:00
"github.com/ClusterCockpit/cc-backend/internal/importer"
2022-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/internal/metricdata"
"github.com/ClusterCockpit/cc-backend/internal/repository"
"github.com/ClusterCockpit/cc-backend/internal/routerConfig"
"github.com/ClusterCockpit/cc-backend/internal/runtimeEnv"
2022-09-05 17:46:38 +02:00
"github.com/ClusterCockpit/cc-backend/pkg/archive"
2022-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/pkg/log"
2022-07-06 14:55:39 +02:00
"github.com/ClusterCockpit/cc-backend/web"
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"
2022-09-16 06:09:55 +02:00
httpSwagger "github.com/swaggo/http-swagger"
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-09-26 13:16:04 +02:00
const logoString = `
2022-09-27 09:56:17 +02:00
____ _ _ ____ _ _ _
/ ___ | | _ _ ___ | | _ ___ _ __ / ___ | ___ ___ | | ___ __ ( _ ) | _
2022-09-26 13:16:04 +02:00
| | | | | | / __ | __ / _ \ ' __ | | / _ \ / __ | | / / ' _ \ | | __ |
2022-09-27 09:56:17 +02:00
| | ___ | | | _ | \ __ \ || __ / | | | __ | ( _ ) | ( __ | < | | _ ) | | | _
2022-09-26 13:16:04 +02:00
\ ____ | _ | \ __ , _ | ___ / \ __ \ ___ | _ | \ ____ \ ___ / \ ___ | _ | \ _ \ . __ / | _ | \ __ |
2022-09-27 09:56:17 +02:00
| _ |
2022-09-26 13:16:04 +02:00
`
var (
buildTime string
hash string
version string
)
2021-12-08 10:15:25 +01:00
func main ( ) {
2023-02-21 10:57:22 +01:00
var flagReinitDB , flagServer , flagSyncLDAP , flagGops , flagMigrateDB , flagDev , flagVersion , flagLogDateTime bool
2023-01-23 18:48:06 +01:00
var flagNewUser , flagDelUser , flagGenJWT , flagConfigFile , flagImportJob , flagLogLevel 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" )
2022-09-15 12:37:44 +02:00
flag . BoolVar ( & flagServer , "server" , false , "Start a server, continues listening on port 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)" )
2022-09-06 08:57:01 +02:00
flag . BoolVar ( & flagDev , "dev" , false , "Enable development components: GraphQL Playground and Swagger UI" )
2022-09-26 13:16:04 +02:00
flag . BoolVar ( & flagVersion , "version" , false , "Show version information and exit" )
2023-02-21 10:57:22 +01:00
flag . BoolVar ( & flagMigrateDB , "migrate-db" , false , "Migrate database to supported version and exit" )
2023-02-09 15:46:07 +01:00
flag . BoolVar ( & flagLogDateTime , "logdate" , false , "Set this flag to add date and time to log messages" )
2022-09-23 15:23:45 +02:00
flag . StringVar ( & flagConfigFile , "config" , "./config.json" , "Specify alternative path to `config.json`" )
2023-02-23 12:33:14 +01:00
flag . StringVar ( & flagNewUser , "add-user" , "" , "Add a new user. Argument format: `<username>:[admin,support,manager,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-09-13 15:20:07 +02:00
flag . StringVar ( & flagImportJob , "import-job" , "" , "Import a job. Argument format: `<path-to-meta.json>:<path-to-data.json>,...`" )
2023-02-25 07:59:37 +01:00
flag . StringVar ( & flagLogLevel , "loglevel" , "warn" , "Sets the logging level: `[debug,info,warn (default),err,fatal,crit]`" )
2021-12-08 10:15:25 +01:00
flag . Parse ( )
2021-03-31 07:23:48 +02:00
2022-09-26 13:16:04 +02:00
if flagVersion {
fmt . Print ( logoString )
fmt . Printf ( "Version:\t%s\n" , version )
fmt . Printf ( "Git hash:\t%s\n" , hash )
fmt . Printf ( "Build time:\t%s\n" , buildTime )
2023-04-12 10:43:46 +02:00
fmt . Printf ( "SQL db version:\t%d\n" , repository . Version )
fmt . Printf ( "Job archive version:\t%d\n" , archive . Version )
2022-09-26 13:16:04 +02:00
os . Exit ( 0 )
}
2023-01-23 18:48:06 +01:00
// Apply config flags for pkg/log
2023-02-09 14:14:58 +01:00
log . Init ( flagLogLevel , flagLogDateTime )
2023-01-23 18:48:06 +01: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-06-21 17:52:36 +02:00
if err := runtimeEnv . LoadEnv ( "./.env" ) ; err != nil && ! os . IsNotExist ( err ) {
2022-01-12 11:13:25 +01:00
log . Fatalf ( "parsing './.env' file failed: %s" , err . Error ( ) )
}
2022-09-05 17:46:38 +02:00
// Initialize sub-modules and handle command line flags.
// The order here is important!
config . Init ( flagConfigFile )
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
2022-09-05 17:46:38 +02:00
// the password for their mysql database in config.json.
if strings . HasPrefix ( config . Keys . DB , "env:" ) {
envvar := strings . TrimPrefix ( config . Keys . DB , "env:" )
config . Keys . DB = os . Getenv ( envvar )
2022-01-31 15:14:37 +01:00
}
2023-02-21 10:57:22 +01:00
if flagMigrateDB {
2023-04-21 12:59:27 +02:00
err := repository . MigrateDB ( config . Keys . DBDriver , config . Keys . DB )
if err != nil {
log . Fatal ( err )
}
2023-02-21 10:57:22 +01:00
os . Exit ( 0 )
}
2022-09-05 17:46:38 +02:00
repository . Connect ( config . Keys . DBDriver , config . Keys . DB )
2022-06-21 17:52:36 +02:00
db := repository . GetConnection ( )
2021-12-16 09:35:03 +01:00
2022-03-02 10:50:08 +01:00
var authentication * auth . Authentication
2022-09-05 17:46:38 +02:00
if ! config . Keys . DisableAuthentication {
var err error
2022-07-07 14:08:37 +02:00
if authentication , err = auth . Init ( db . DB , map [ string ] interface { } {
2022-09-05 17:46:38 +02:00
"ldap" : config . Keys . LdapConfig ,
"jwt" : config . Keys . JwtConfig ,
2022-07-07 14:08:37 +02:00
} ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "auth initialization failed: %v" , err )
2022-02-16 11:50:25 +01:00
}
2022-09-05 17:46:38 +02:00
if d , err := time . ParseDuration ( config . Keys . SessionMaxAge ) ; err != nil {
2022-07-07 14:08:37 +02:00
authentication . SessionMaxAge = d
2021-12-08 10:15:25 +01:00
}
2021-03-31 07:23:48 +02:00
2021-12-08 10:15:25 +01:00
if flagNewUser != "" {
2022-07-07 14:08:37 +02:00
parts := strings . SplitN ( flagNewUser , ":" , 3 )
if len ( parts ) != 3 || len ( parts [ 0 ] ) == 0 {
log . Fatal ( "invalid argument format for user creation" )
}
if err := authentication . AddUser ( & auth . User {
2023-02-23 12:33:14 +01:00
Username : parts [ 0 ] , Projects : make ( [ ] string , 0 ) , Password : parts [ 2 ] , Roles : strings . Split ( parts [ 1 ] , "," ) ,
2022-07-07 14:08:37 +02:00
} ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "adding '%s' user authentication failed: %v" , parts [ 0 ] , err )
2021-12-08 10:15:25 +01:00
}
}
if flagDelUser != "" {
2022-02-14 14:22:44 +01:00
if err := authentication . DelUser ( flagDelUser ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "deleting user failed: %v" , err )
2021-12-08 10:15:25 +01:00
}
}
2021-11-26 10:34:29 +01:00
2021-12-08 10:15:25 +01:00
if flagSyncLDAP {
2022-07-07 14:08:37 +02:00
if authentication . LdapAuth == nil {
log . Fatal ( "cannot sync: LDAP authentication is not configured" )
}
if err := authentication . LdapAuth . Sync ( ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "LDAP sync failed: %v" , err )
2022-02-14 14:22:44 +01:00
}
2022-07-25 09:33:36 +02:00
log . Info ( "LDAP sync successfull" )
2021-12-08 10:15:25 +01:00
}
2022-01-10 16:14:54 +01:00
if flagGenJWT != "" {
2022-07-07 14:08:37 +02:00
user , err := authentication . GetUser ( flagGenJWT )
2022-01-10 16:14:54 +01:00
if err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "could not get user from JWT: %v" , err )
2022-01-10 16:14:54 +01:00
}
2022-01-27 09:29:11 +01:00
if ! user . HasRole ( auth . RoleApi ) {
2023-01-23 18:48:06 +01:00
log . Warnf ( "user '%s' does not have the API role" , user . Username )
2022-01-10 16:14:54 +01:00
}
2022-07-07 14:08:37 +02:00
jwt , err := authentication . JwtAuth . ProvideJWT ( user )
2022-01-10 16:14:54 +01:00
if err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "failed to provide JWT to user '%s': %v" , user . Username , err )
2022-01-10 16:14:54 +01:00
}
2023-01-20 09:49:17 +01:00
fmt . Printf ( "MAIN > JWT for '%s': %s\n" , user . Username , jwt )
2022-01-10 16:14:54 +01:00
}
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
}
2022-11-08 16:49:45 +01:00
if err := archive . Init ( config . Keys . Archive , config . Keys . DisableArchive ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "failed to initialize archive: %s" , err . Error ( ) )
2021-12-08 10:15:25 +01:00
}
2021-10-26 10:24:43 +02:00
2022-09-05 17:46:38 +02:00
if err := metricdata . Init ( config . Keys . DisableArchive ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "failed to initialize metricdata repository: %s" , err . Error ( ) )
2021-10-26 10:24:43 +02:00
}
2021-12-08 10:15:25 +01:00
if flagReinitDB {
2023-04-28 08:49:58 +02:00
if err := importer . InitDB ( ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "failed to re-initialize repository DB: %s" , err . Error ( ) )
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-09-13 15:20:07 +02:00
if flagImportJob != "" {
2023-04-28 08:49:58 +02:00
if err := importer . HandleImportFlag ( flagImportJob ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "job import failed: %s" , err . Error ( ) )
2022-09-13 15:20:07 +02:00
}
}
2022-09-11 07:08:28 +02:00
if ! flagServer {
2021-12-08 10:15:25 +01:00
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
2022-09-05 17:46:38 +02:00
jobRepo := repository . GetJobRepository ( )
2022-06-21 17:52:36 +02:00
resolver := & graph . Resolver { DB : 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 :
2023-01-20 09:49:17 +01:00
return fmt . Errorf ( "MAIN > Panic: %s" , e )
2022-01-20 10:00:55 +01:00
case error :
2023-01-20 09:49:17 +01:00
return fmt . Errorf ( "MAIN > Panic caused by: %w" , e )
2022-01-20 10:00:55 +01:00
}
2022-01-10 16:14:54 +01:00
2023-01-20 09:49:17 +01:00
return errors . New ( "MAIN > Internal server error (panic)" )
2022-01-20 10:00:55 +01:00
} )
}
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 ,
2022-09-05 17:46:38 +02:00
MachineStateDir : config . Keys . 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-09-27 09:56:17 +02:00
buildInfo := web . Build { Version : version , Hash : hash , Buildtime : buildTime }
2021-12-08 10:15:25 +01:00
2022-03-15 08:29:29 +01:00
r . HandleFunc ( "/login" , func ( rw http . ResponseWriter , r * http . Request ) {
2022-07-05 10:40:12 +02:00
rw . Header ( ) . Add ( "Content-Type" , "text/html; charset=utf-8" )
2022-09-27 09:56:17 +02:00
web . RenderTemplate ( rw , r , "login.tmpl" , & web . Page { Title : "Login" , Build : buildInfo } )
2022-03-15 08:29:29 +01:00
} ) . Methods ( http . MethodGet )
2022-02-03 09:39:04 +01:00
r . HandleFunc ( "/imprint" , func ( rw http . ResponseWriter , r * http . Request ) {
2022-07-05 10:40:12 +02:00
rw . Header ( ) . Add ( "Content-Type" , "text/html; charset=utf-8" )
2022-09-27 09:56:17 +02:00
web . RenderTemplate ( rw , r , "imprint.tmpl" , & web . Page { Title : "Imprint" , Build : buildInfo } )
2022-02-03 09:39:04 +01:00
} )
r . HandleFunc ( "/privacy" , func ( rw http . ResponseWriter , r * http . Request ) {
2022-07-05 10:40:12 +02:00
rw . Header ( ) . Add ( "Content-Type" , "text/html; charset=utf-8" )
2022-09-27 09:56:17 +02:00
web . RenderTemplate ( rw , r , "privacy.tmpl" , & web . Page { Title : "Privacy" , Build : buildInfo } )
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 ( )
2022-09-05 17:46:38 +02:00
if ! config . Keys . 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-07-05 10:40:12 +02:00
rw . Header ( ) . Add ( "Content-Type" , "text/html; charset=utf-8" )
2022-02-14 14:22:44 +01:00
rw . WriteHeader ( http . StatusUnauthorized )
2022-07-06 15:00:08 +02:00
web . RenderTemplate ( rw , r , "login.tmpl" , & web . Page {
2022-02-14 14:22:44 +01:00
Title : "Login failed - ClusterCockpit" ,
2022-02-16 09:01:47 +01:00
Error : err . Error ( ) ,
2022-09-27 09:56:17 +02:00
Build : buildInfo ,
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 ) {
2022-07-05 10:40:12 +02:00
rw . Header ( ) . Add ( "Content-Type" , "text/html; charset=utf-8" )
2022-02-16 09:01:47 +01:00
rw . WriteHeader ( http . StatusOK )
2022-07-06 15:00:08 +02:00
web . RenderTemplate ( rw , r , "login.tmpl" , & web . Page {
2022-02-16 09:01:47 +01:00
Title : "Bye - ClusterCockpit" ,
Info : "Logout sucessful" ,
2022-09-27 09:56:17 +02:00
Build : buildInfo ,
2022-02-16 09:01:47 +01:00
} )
} ) ) ) . 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 )
2022-07-06 15:00:08 +02:00
web . RenderTemplate ( rw , r , "login.tmpl" , & web . Page {
2022-02-14 14:22:44 +01:00
Title : "Authentication failed - ClusterCockpit" ,
Error : err . Error ( ) ,
2022-09-27 09:56:17 +02:00
Build : buildInfo ,
2022-02-14 14:22:44 +01:00
} )
} )
} )
2021-12-08 10:15:25 +01:00
}
2022-03-15 08:29:29 +01:00
2022-09-06 08:57:01 +02:00
if flagDev {
r . Handle ( "/playground" , playground . Handler ( "GraphQL playground" , "/query" ) )
2022-09-16 06:09:55 +02:00
r . PathPrefix ( "/swagger/" ) . Handler ( httpSwagger . Handler (
2023-02-17 15:45:31 +01:00
httpSwagger . URL ( "http://" + config . Keys . Addr + "/swagger/doc.json" ) ) ) . Methods ( http . MethodGet )
2022-09-06 08:57:01 +02:00
}
2021-12-08 10:15:25 +01:00
secured . Handle ( "/query" , graphQLEndpoint )
2021-12-16 09:35:03 +01:00
2022-12-14 10:02:22 +01:00
// Send a searchId and then reply with a redirect to a user, or directly send query to job table for jobid and project.
2022-02-09 15:03:12 +01:00
secured . HandleFunc ( "/search" , func ( rw http . ResponseWriter , r * http . Request ) {
2023-02-09 10:11:11 +01:00
routerConfig . HandleSearchBar ( rw , r , api )
2022-02-09 15:03:12 +01:00
} )
2022-03-15 08:29:29 +01:00
// Mount all /monitoring/... and /api/... routes.
2022-09-27 09:56:17 +02:00
routerConfig . SetupRoutes ( secured , version , hash , buildTime )
2021-12-17 15:49:22 +01:00
api . MountRoutes ( secured )
2021-12-08 15:50:03 +01:00
2022-09-05 17:46:38 +02:00
if config . Keys . EmbedStaticFiles {
2022-07-06 14:55:39 +02:00
r . PathPrefix ( "/" ) . Handler ( web . ServeFiles ( ) )
} else {
2022-09-05 17:46:38 +02:00
r . PathPrefix ( "/" ) . Handler ( http . FileServer ( http . Dir ( config . Keys . StaticFiles ) ) )
2022-07-06 14:55:39 +02:00
}
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 (
2022-07-28 18:07:30 +02:00
handlers . AllowCredentials ( ) ,
handlers . AllowedHeaders ( [ ] string { "X-Requested-With" , "Content-Type" , "Authorization" , "Origin" } ) ,
2021-12-08 10:15:25 +01:00
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 ,
2022-09-05 17:46:38 +02:00
Addr : config . Keys . Addr ,
2022-01-12 11:13:25 +01:00
}
2021-12-08 10:15:25 +01:00
// Start http or https server
2022-09-05 17:46:38 +02:00
listener , err := net . Listen ( "tcp" , config . Keys . Addr )
2022-01-12 11:13:25 +01:00
if err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "starting http listener failed: %v" , err )
2022-01-12 11:13:25 +01:00
}
2022-09-05 17:46:38 +02:00
if ! strings . HasSuffix ( config . Keys . Addr , ":80" ) && config . Keys . RedirectHttpTo != "" {
2022-03-14 08:45:17 +01:00
go func ( ) {
2022-09-05 17:46:38 +02:00
http . ListenAndServe ( ":80" , http . RedirectHandler ( config . Keys . RedirectHttpTo , http . StatusMovedPermanently ) )
2022-03-14 08:45:17 +01:00
} ( )
}
2022-09-05 17:46:38 +02:00
if config . Keys . HttpsCertFile != "" && config . Keys . HttpsKeyFile != "" {
cert , err := tls . LoadX509KeyPair ( config . Keys . HttpsCertFile , config . Keys . HttpsKeyFile )
2022-01-12 11:13:25 +01:00
if err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "loading X509 keypair failed: %v" , err )
2022-01-12 11:13:25 +01:00
}
listener = tls . NewListener ( listener , & tls . Config {
Certificates : [ ] tls . Certificate { cert } ,
2022-06-17 10:08:31 +02:00
CipherSuites : [ ] uint16 {
tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
} ,
MinVersion : tls . VersionTLS12 ,
PreferServerCipherSuites : true ,
2022-01-12 11:13:25 +01:00
} )
2023-02-25 08:28:43 +01:00
fmt . Printf ( "HTTPS server listening at %s..." , config . Keys . Addr )
2021-12-08 10:15:25 +01:00
} else {
2023-02-25 08:28:43 +01:00
fmt . Printf ( "HTTP server listening at %s..." , config . Keys . Addr )
2022-01-12 11:13:25 +01:00
}
// 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,
2023-01-20 09:49:17 +01:00
// the actual http server can be started.
2022-09-05 17:46:38 +02:00
if err := runtimeEnv . DropPrivileges ( config . Keys . Group , config . Keys . User ) ; err != nil {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "error while preparing server start: %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 {
2023-01-23 18:48:06 +01:00
log . Fatalf ( "starting server failed: %v" , err )
2022-01-12 11:13:25 +01:00
}
} ( )
wg . Add ( 1 )
sigs := make ( chan os . Signal , 1 )
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )
go func ( ) {
defer wg . Done ( )
<- sigs
2023-01-20 09:49:17 +01:00
runtimeEnv . SystemdNotifiy ( false , "Shutting down ..." )
2022-01-12 11:13:25 +01:00
// First shut down the server gracefully (waiting for all ongoing requests)
server . Shutdown ( context . Background ( ) )
// Then, wait for any async archivings still pending...
2022-12-08 15:04:58 +01:00
api . JobRepository . WaitForArchiving ( )
2022-01-12 11:13:25 +01:00
} ( )
2022-09-05 17:46:38 +02:00
if config . Keys . StopJobsExceedingWalltime > 0 {
2022-05-09 11:53:41 +02:00
go func ( ) {
for range time . Tick ( 30 * time . Minute ) {
2022-09-05 17:46:38 +02:00
err := jobRepo . StopJobsExceedingWalltimeBy ( config . Keys . StopJobsExceedingWalltime )
2022-05-09 11:53:41 +02:00
if err != nil {
2023-02-01 11:58:27 +01:00
log . Warnf ( "Error while looking for jobs exceeding their walltime: %s" , err . Error ( ) )
2022-05-09 11:53:41 +02:00
}
runtime . GC ( )
}
} ( )
}
2022-04-07 09:50:32 +02:00
2022-03-24 10:35:52 +01:00
if os . Getenv ( "GOGC" ) == "" {
debug . SetGCPercent ( 25 )
}
2022-06-21 17:52:36 +02:00
runtimeEnv . SystemdNotifiy ( true , "running" )
2022-01-12 11:13:25 +01:00
wg . Wait ( )
log . Print ( "Gracefull shutdown completed!" )
2021-10-26 10:24:43 +02:00
}