2018-02-19 22:24:10 +00:00
|
|
|
// Copyright 2018 Drone.IO Inc.
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2018-03-21 13:02:17 +00:00
|
|
|
//
|
2018-02-19 22:24:10 +00:00
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-06-29 22:51:22 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-07-13 23:46:01 +00:00
|
|
|
"context"
|
2017-07-24 23:15:25 +00:00
|
|
|
"crypto/tls"
|
2023-11-01 10:44:08 +00:00
|
|
|
"errors"
|
2024-01-10 14:34:44 +00:00
|
|
|
"fmt"
|
2017-06-29 22:51:22 +00:00
|
|
|
"net/http"
|
2021-09-26 22:22:23 +00:00
|
|
|
"net/http/httputil"
|
2017-06-29 22:51:22 +00:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-09-04 01:24:42 +00:00
|
|
|
"github.com/caddyserver/certmagic"
|
2021-12-12 21:49:30 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
2024-06-04 06:30:54 +00:00
|
|
|
prometheus_http "github.com/prometheus/client_golang/prometheus/promhttp"
|
2021-10-12 07:25:13 +00:00
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/rs/zerolog/log"
|
2021-10-27 19:03:14 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2021-10-12 07:25:13 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2023-12-08 07:15:08 +00:00
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/server"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/server/cron"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/server/router"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/server/web"
|
2023-12-31 22:29:56 +00:00
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/shared/logger"
|
2024-07-13 23:46:01 +00:00
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
|
2023-12-08 07:15:08 +00:00
|
|
|
"go.woodpecker-ci.org/woodpecker/v2/version"
|
2017-06-29 22:51:22 +00:00
|
|
|
)
|
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
const (
|
|
|
|
shutdownTimeout = time.Second * 5
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
stopServerFunc context.CancelCauseFunc = func(error) {}
|
|
|
|
shutdownCancelFunc context.CancelFunc = func() {}
|
|
|
|
shutdownCtx = context.Background()
|
|
|
|
)
|
|
|
|
|
2021-11-23 14:36:52 +00:00
|
|
|
func run(c *cli.Context) error {
|
2024-01-10 14:34:44 +00:00
|
|
|
if err := logger.SetupGlobalLogger(c, true); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
ctx := utils.WithContextSigtermCallback(c.Context, func() {
|
|
|
|
log.Info().Msg("termination signal is received, shutting down server")
|
|
|
|
})
|
|
|
|
|
|
|
|
ctx, ctxCancel := context.WithCancelCause(ctx)
|
|
|
|
stopServerFunc = func(err error) {
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("shutdown of whole server")
|
|
|
|
}
|
|
|
|
stopServerFunc = func(error) {}
|
|
|
|
shutdownCtx, shutdownCancelFunc = context.WithTimeout(shutdownCtx, shutdownTimeout)
|
|
|
|
ctxCancel(err)
|
|
|
|
}
|
|
|
|
defer stopServerFunc(nil)
|
|
|
|
defer shutdownCancelFunc()
|
|
|
|
|
2023-08-07 18:47:30 +00:00
|
|
|
// set gin mode based on log level
|
|
|
|
if zerolog.GlobalLevel() > zerolog.DebugLevel {
|
2021-12-12 21:49:30 +00:00
|
|
|
gin.SetMode(gin.ReleaseMode)
|
2021-12-08 22:40:00 +00:00
|
|
|
}
|
2021-10-16 22:41:36 +00:00
|
|
|
|
2017-07-12 18:48:56 +00:00
|
|
|
if c.String("server-host") == "" {
|
2024-01-10 14:34:44 +00:00
|
|
|
return fmt.Errorf("WOODPECKER_HOST is not properly configured")
|
2017-07-12 18:48:56 +00:00
|
|
|
}
|
|
|
|
|
2017-12-20 02:08:55 +00:00
|
|
|
if !strings.Contains(c.String("server-host"), "://") {
|
2024-01-10 14:34:44 +00:00
|
|
|
return fmt.Errorf("WOODPECKER_HOST must be <scheme>://<hostname> format")
|
2017-12-20 02:08:55 +00:00
|
|
|
}
|
|
|
|
|
2023-09-23 05:54:23 +00:00
|
|
|
if _, err := url.Parse(c.String("server-host")); err != nil {
|
2024-01-10 14:34:44 +00:00
|
|
|
return fmt.Errorf("could not parse WOODPECKER_HOST: %w", err)
|
2023-09-23 05:54:23 +00:00
|
|
|
}
|
|
|
|
|
2021-08-20 14:32:52 +00:00
|
|
|
if strings.Contains(c.String("server-host"), "://localhost") {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Warn().Msg(
|
Clean up config environment variables for server and agent (#218)
The goal here is to make consistent use of configuration environment variables prefixed `WOODPECKER_`. Where several variants existed, this PR aims to remove all but one option, leaving the most explicit.
This PR only changes server and agent code, but not documentation, in order to keep the PR digestible. Once we have consensus that this is correct, I'll change docs accordingly.
User (rather: admin) facing changes in this PR:
- In general, support for all server and agent config environment variables (env vars) starting with `DRONE_` is removed. The according `WOODPECKER_*` variables must be used instead.
- The env var `WOODPECKER_HOST` replaces `DRONE_HOST`, and `DRONE_SERVER_HOST`.
- The env var `WOODPECKER_AGENT_SECRET` is used to configure the shared secret which agents use to authenticate against the server. It replaces `WOODPECKER_SECRET`, `DRONE_SECRET`, `WOODPECKER_PASSWORD`, `DRONE_PASSWORD`, and `DRONE_AGENT_SECRET`.
- The env var `WOODPECKER_DATABASE_DRIVER` replaces `DRONE_DATABASE_DRIVER` and `DATABASE_DRIVER`.
- The env var `WOODPECKER_DATABASE_DATASOURCE` replaces `DRONE_DATABASE_DATASOURCE` and `DATABASE_CONFIG`.
2021-09-28 13:43:44 +00:00
|
|
|
"WOODPECKER_HOST should probably be publicly accessible (not localhost)",
|
2021-08-20 14:32:52 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
_store, err := setupStore(ctx, c)
|
2024-01-10 14:34:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("can't setup store: %w", err)
|
|
|
|
}
|
2021-11-13 19:18:06 +00:00
|
|
|
defer func() {
|
2021-12-01 13:22:06 +00:00
|
|
|
if err := _store.Close(); err != nil {
|
2021-11-13 19:18:06 +00:00
|
|
|
log.Error().Err(err).Msg("could not close store")
|
|
|
|
}
|
|
|
|
}()
|
2021-10-19 09:44:49 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
err = setupEvilGlobals(ctx, c, _store)
|
2023-12-24 12:26:23 +00:00
|
|
|
if err != nil {
|
2024-01-10 14:34:44 +00:00
|
|
|
return fmt.Errorf("can't setup globals: %w", err)
|
2023-12-24 12:26:23 +00:00
|
|
|
}
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
// wait for all services until one do stops with an error
|
|
|
|
serviceWaitingGroup := errgroup.Group{}
|
2017-07-31 19:15:05 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
log.Info().Msgf("starting Woodpecker server with version '%s'", version.String())
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
startMetricsCollector(ctx, _store)
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
log.Info().Msg("starting cron service ...")
|
|
|
|
if err := cron.Run(ctx, _store); err != nil {
|
|
|
|
go stopServerFunc(err)
|
|
|
|
return err
|
2017-06-29 22:51:22 +00:00
|
|
|
}
|
2024-07-13 23:46:01 +00:00
|
|
|
log.Info().Msg("cron service stopped")
|
|
|
|
return nil
|
|
|
|
})
|
2023-01-28 13:13:04 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
// start the grpc server
|
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
log.Info().Msg("starting grpc server ...")
|
|
|
|
if err := runGrpcServer(ctx, c, _store); err != nil {
|
|
|
|
// stop whole server as grpc is essential
|
|
|
|
go stopServerFunc(err)
|
|
|
|
return err
|
2017-06-29 22:51:22 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2022-09-03 18:41:23 +00:00
|
|
|
proxyWebUI := c.String("www-proxy")
|
|
|
|
var webUIServe func(w http.ResponseWriter, r *http.Request)
|
2019-05-30 10:15:29 +00:00
|
|
|
|
2022-09-03 18:41:23 +00:00
|
|
|
if proxyWebUI == "" {
|
2023-03-20 23:48:15 +00:00
|
|
|
webEngine, err := web.New()
|
|
|
|
if err != nil {
|
2024-01-10 14:34:44 +00:00
|
|
|
log.Error().Err(err).Msg("failed to create web engine")
|
|
|
|
return err
|
2023-03-20 23:48:15 +00:00
|
|
|
}
|
|
|
|
webUIServe = webEngine.ServeHTTP
|
2022-09-03 18:41:23 +00:00
|
|
|
} else {
|
|
|
|
origin, _ := url.Parse(proxyWebUI)
|
|
|
|
|
|
|
|
director := func(req *http.Request) {
|
|
|
|
req.Header.Add("X-Forwarded-Host", req.Host)
|
|
|
|
req.Header.Add("X-Origin-Host", origin.Host)
|
|
|
|
req.URL.Scheme = origin.Scheme
|
|
|
|
req.URL.Host = origin.Host
|
|
|
|
}
|
|
|
|
|
|
|
|
proxy := &httputil.ReverseProxy{Director: director}
|
|
|
|
webUIServe = proxy.ServeHTTP
|
|
|
|
}
|
|
|
|
|
|
|
|
// setup the server and start the listener
|
|
|
|
handler := router.Load(
|
|
|
|
webUIServe,
|
|
|
|
middleware.Logger(time.RFC3339, true),
|
|
|
|
middleware.Version,
|
2024-01-19 15:20:35 +00:00
|
|
|
middleware.Store(_store),
|
2022-09-03 18:41:23 +00:00
|
|
|
)
|
2022-08-31 22:36:32 +00:00
|
|
|
|
2024-01-10 14:34:44 +00:00
|
|
|
switch {
|
|
|
|
case c.String("server-cert") != "":
|
2022-09-03 18:41:23 +00:00
|
|
|
// start the server with tls enabled
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
tlsServer := &http.Server{
|
2023-05-11 04:11:10 +00:00
|
|
|
Addr: server.Config.Server.PortTLS,
|
2017-09-19 22:30:31 +00:00
|
|
|
Handler: handler,
|
|
|
|
TLSConfig: &tls.Config{
|
2021-10-19 09:44:49 +00:00
|
|
|
NextProtos: []string{"h2", "http/1.1"},
|
2017-09-19 22:30:31 +00:00
|
|
|
},
|
|
|
|
}
|
2024-07-13 23:46:01 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Info().Msg("shutdown tls server ...")
|
|
|
|
if err := tlsServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck
|
|
|
|
log.Error().Err(err).Msg("shutdown tls server failed")
|
|
|
|
} else {
|
|
|
|
log.Info().Msg("tls server stopped")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
log.Info().Msg("starting tls server ...")
|
|
|
|
err := tlsServer.ListenAndServeTLS(
|
2017-07-26 14:44:38 +00:00
|
|
|
c.String("server-cert"),
|
|
|
|
c.String("server-key"),
|
|
|
|
)
|
2023-11-01 10:44:08 +00:00
|
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
2024-07-13 23:46:01 +00:00
|
|
|
log.Error().Err(err).Msg("TLS server failed")
|
|
|
|
stopServerFunc(fmt.Errorf("TLS server failed: %w", err))
|
2023-11-01 10:44:08 +00:00
|
|
|
}
|
|
|
|
return err
|
2017-07-26 14:44:38 +00:00
|
|
|
})
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2022-09-03 18:41:23 +00:00
|
|
|
// http to https redirect
|
2022-09-04 01:24:42 +00:00
|
|
|
redirect := func(w http.ResponseWriter, req *http.Request) {
|
2023-09-23 05:54:23 +00:00
|
|
|
serverURL, _ := url.Parse(server.Config.Server.Host)
|
2022-09-04 01:24:42 +00:00
|
|
|
req.URL.Scheme = "https"
|
2023-09-23 05:54:23 +00:00
|
|
|
req.URL.Host = serverURL.Host
|
2022-09-04 01:24:42 +00:00
|
|
|
|
|
|
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
|
|
|
|
|
|
|
http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently)
|
|
|
|
}
|
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
redirectServer := &http.Server{
|
|
|
|
Addr: server.Config.Server.Port,
|
|
|
|
Handler: http.HandlerFunc(redirect),
|
2023-11-01 10:44:08 +00:00
|
|
|
}
|
2024-07-13 23:46:01 +00:00
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Info().Msg("shutdown redirect server ...")
|
|
|
|
if err := redirectServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck
|
|
|
|
log.Error().Err(err).Msg("shutdown redirect server failed")
|
|
|
|
} else {
|
|
|
|
log.Info().Msg("redirect server stopped")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
log.Info().Msg("starting redirect server ...")
|
|
|
|
if err := redirectServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
log.Error().Err(err).Msg("redirect server failed")
|
|
|
|
stopServerFunc(fmt.Errorf("redirect server failed: %w", err))
|
|
|
|
}
|
|
|
|
return nil
|
2022-09-03 18:41:23 +00:00
|
|
|
})
|
2024-01-10 14:34:44 +00:00
|
|
|
case c.Bool("lets-encrypt"):
|
2022-09-03 18:41:23 +00:00
|
|
|
// start the server with lets-encrypt
|
2022-09-04 01:24:42 +00:00
|
|
|
certmagic.DefaultACME.Email = c.String("lets-encrypt-email")
|
|
|
|
certmagic.DefaultACME.Agreed = true
|
|
|
|
|
2023-10-07 13:39:19 +00:00
|
|
|
address, err := url.Parse(strings.TrimSuffix(c.String("server-host"), "/"))
|
2022-09-03 18:41:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-29 22:51:22 +00:00
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Error().Msg("there is no certmagic.HTTPS alternative who is context aware we will fail in 2 seconds")
|
|
|
|
time.Sleep(time.Second * 2)
|
|
|
|
log.Fatal().Msg("we kill certmagic by fail") //nolint:forbidigo
|
|
|
|
}()
|
|
|
|
|
|
|
|
log.Info().Msg("starting certmagic server ...")
|
2022-09-04 01:24:42 +00:00
|
|
|
if err := certmagic.HTTPS([]string{address.Host}, handler); err != nil {
|
2024-07-13 23:46:01 +00:00
|
|
|
log.Error().Err(err).Msg("certmagic does not work")
|
|
|
|
stopServerFunc(fmt.Errorf("certmagic failed: %w", err))
|
2022-09-03 18:41:23 +00:00
|
|
|
}
|
2022-09-04 01:24:42 +00:00
|
|
|
return nil
|
2022-09-03 18:41:23 +00:00
|
|
|
})
|
2024-01-10 14:34:44 +00:00
|
|
|
default:
|
2022-09-03 18:41:23 +00:00
|
|
|
// start the server without tls
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
|
|
|
httpServer := &http.Server{
|
|
|
|
Addr: c.String("server-addr"),
|
|
|
|
Handler: handler,
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Info().Msg("shutdown http server ...")
|
|
|
|
if err := httpServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck
|
|
|
|
log.Error().Err(err).Msg("shutdown http server failed")
|
|
|
|
} else {
|
|
|
|
log.Info().Msg("http server stopped")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
log.Info().Msg("starting http server ...")
|
|
|
|
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
log.Error().Err(err).Msg("http server failed")
|
|
|
|
stopServerFunc(fmt.Errorf("http server failed: %w", err))
|
2023-11-01 10:44:08 +00:00
|
|
|
}
|
|
|
|
return err
|
2022-09-03 18:41:23 +00:00
|
|
|
})
|
2018-01-13 04:54:49 +00:00
|
|
|
}
|
2022-09-03 18:41:23 +00:00
|
|
|
|
2023-03-12 08:41:10 +00:00
|
|
|
if metricsServerAddr := c.String("metrics-server-addr"); metricsServerAddr != "" {
|
2024-07-13 23:46:01 +00:00
|
|
|
serviceWaitingGroup.Go(func() error {
|
2023-03-12 08:41:10 +00:00
|
|
|
metricsRouter := gin.New()
|
2024-06-04 06:30:54 +00:00
|
|
|
metricsRouter.GET("/metrics", gin.WrapH(prometheus_http.Handler()))
|
2024-07-13 23:46:01 +00:00
|
|
|
|
|
|
|
metricsServer := &http.Server{
|
|
|
|
Addr: metricsServerAddr,
|
|
|
|
Handler: metricsRouter,
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Info().Msg("shutdown metrics server ...")
|
|
|
|
if err := metricsServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck
|
|
|
|
log.Error().Err(err).Msg("shutdown metrics server failed")
|
|
|
|
} else {
|
|
|
|
log.Info().Msg("metrics server stopped")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
log.Info().Msg("starting metrics server ...")
|
|
|
|
if err := metricsServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
log.Error().Err(err).Msg("metrics server failed")
|
|
|
|
stopServerFunc(fmt.Errorf("metrics server failed: %w", err))
|
2023-11-01 10:44:08 +00:00
|
|
|
}
|
|
|
|
return err
|
2023-03-12 08:41:10 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-07-13 23:46:01 +00:00
|
|
|
return serviceWaitingGroup.Wait()
|
2017-06-29 22:51:22 +00:00
|
|
|
}
|