diff --git a/cmd/web.go b/cmd/web.go new file mode 100644 index 0000000..43f4900 --- /dev/null +++ b/cmd/web.go @@ -0,0 +1,273 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "strings" + + _ "net/http/pprof" // Used for debugging if enabled and a web server is running + + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/routers/install" + hat_routers "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers" + "github.com/felixge/fgprof" + "github.com/urfave/cli" + ini "gopkg.in/ini.v1" +) + +// CmdWeb represents the available web sub-command. +var CmdWeb = cli.Command{ + Name: "web", + Usage: "Start Gitea web server", + Description: `Gitea web server is the only thing you need to run, +and it takes care of all the other things for you`, + Action: runWeb, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "port, p", + Value: "3000", + Usage: "Temporary port number to prevent conflict", + }, + cli.StringFlag{ + Name: "install-port", + Value: "3000", + Usage: "Temporary port number to run the install page on to prevent conflict", + }, + cli.StringFlag{ + Name: "pid, P", + Value: setting.PIDFile, + Usage: "Custom pid file path", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Only display Fatal logging errors until logging is set-up", + }, + cli.BoolFlag{ + Name: "verbose", + Usage: "Set initial logging to TRACE level until logging is properly set-up", + }, + }, +} + +func runHTTPRedirector() { + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true) + defer finished() + + source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect) + dest := strings.TrimSuffix(setting.AppURL, "/") + log.Info("Redirecting: %s to %s", source, dest) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + target := dest + r.URL.Path + if len(r.URL.RawQuery) > 0 { + target += "?" + r.URL.RawQuery + } + http.Redirect(w, r, target, http.StatusTemporaryRedirect) + }) + + err := runHTTP("tcp", source, "HTTP Redirector", handler) + if err != nil { + log.Fatal("Failed to start port redirection: %v", err) + } +} + +func runWeb(ctx *cli.Context) error { + if ctx.Bool("verbose") { + _ = log.DelLogger("console") + log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) + } else if ctx.Bool("quiet") { + _ = log.DelLogger("console") + log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "fatal", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) + } + defer func() { + if panicked := recover(); panicked != nil { + log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) + } + }() + + managerCtx, cancel := context.WithCancel(context.Background()) + graceful.InitManager(managerCtx) + defer cancel() + + if os.Getppid() > 1 && len(os.Getenv("LISTEN_FDS")) > 0 { + log.Info("Restarting Gitea on PID: %d from parent PID: %d", os.Getpid(), os.Getppid()) + } else { + log.Info("Starting Gitea on PID: %d", os.Getpid()) + } + + // Set pid file setting + if ctx.IsSet("pid") { + setting.PIDFile = ctx.String("pid") + setting.WritePIDFile = true + } + + // Perform pre-initialization + needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext()) + if needsInstall { + // Flag for port number in case first time run conflict + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + if ctx.IsSet("install-port") { + if err := setPort(ctx.String("install-port")); err != nil { + return err + } + } + c := install.Routes() + err := listen(c, false) + if err != nil { + log.Critical("Unable to open listener for installer. Is Gitea already running?") + graceful.GetManager().DoGracefulShutdown() + } + select { + case <-graceful.GetManager().IsShutdown(): + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err + default: + } + } else { + NoInstallListener() + } + + if setting.EnablePprof { + go func() { + http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) + _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true) + log.Info("Starting pprof server on localhost:6060") + log.Info("%v", http.ListenAndServe("localhost:6060", nil)) + finished() + }() + } + + log.Info("Global init") + // Perform global initialization + setting.LoadFromExisting() + routers.GlobalInitInstalled(graceful.GetManager().HammerContext()) + + // We check that AppDataPath exists here (it should have been created during installation) + // We can't check it in `GlobalInitInstalled`, because some integration tests + // use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests. + if _, err := os.Stat(setting.AppDataPath); err != nil { + log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath) + } + + // Override the provided port number within the configuration + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + + // Set up Chi routes + c := routers.NormalRoutes() + hat_routers.InitHatRouters(c) + err := listen(c, true) + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err +} + +func setPort(port string) error { + setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1) + setting.HTTPPort = port + + switch setting.Protocol { + case setting.HTTPUnix: + case setting.FCGI: + case setting.FCGIUnix: + default: + defaultLocalURL := string(setting.Protocol) + "://" + if setting.HTTPAddr == "0.0.0.0" { + defaultLocalURL += "localhost" + } else { + defaultLocalURL += setting.HTTPAddr + } + defaultLocalURL += ":" + setting.HTTPPort + "/" + + // Save LOCAL_ROOT_URL if port changed + setting.CreateOrAppendToCustomConf(func(cfg *ini.File) { + cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) + }) + } + return nil +} + +func listen(m http.Handler, handleRedirector bool) error { + listenAddr := setting.HTTPAddr + if setting.Protocol != setting.HTTPUnix && setting.Protocol != setting.FCGIUnix { + listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort) + } + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: Gitea Server", process.SystemProcessType, true) + defer finished() + log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL) + // This can be useful for users, many users do wrong to their config and get strange behaviors behind a reverse-proxy. + // A user may fix the configuration mistake when he sees this log. + // And this is also very helpful to maintainers to provide help to users to resolve their configuration problems. + log.Info("AppURL(ROOT_URL): %s", setting.AppURL) + + if setting.LFS.StartServer { + log.Info("LFS server enabled") + } + + var err error + switch setting.Protocol { + case setting.HTTP: + if handleRedirector { + NoHTTPRedirector() + } + err = runHTTP("tcp", listenAddr, "Web", m) + case setting.HTTPS: + if setting.EnableAcme { + err = runACME(listenAddr, m) + break + } else { + if handleRedirector { + if setting.RedirectOtherPort { + go runHTTPRedirector() + } else { + NoHTTPRedirector() + } + } + err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m) + } + case setting.FCGI: + if handleRedirector { + NoHTTPRedirector() + } + err = runFCGI("tcp", listenAddr, "FCGI Web", m) + case setting.HTTPUnix: + if handleRedirector { + NoHTTPRedirector() + } + err = runHTTP("unix", listenAddr, "Web", m) + case setting.FCGIUnix: + if handleRedirector { + NoHTTPRedirector() + } + err = runFCGI("unix", listenAddr, "Web", m) + default: + log.Fatal("Invalid protocol: %s", setting.Protocol) + } + + if err != nil { + log.Critical("Failed to start server: %v", err) + } + log.Info("HTTP Listener: %s Closed", listenAddr) + return err +} diff --git a/cmd/web_acme.go b/cmd/web_acme.go new file mode 100644 index 0000000..57b400d --- /dev/null +++ b/cmd/web_acme.go @@ -0,0 +1,136 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "net/http" + "os" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" + + "github.com/caddyserver/certmagic" +) + +func getCARoot(path string) (*x509.CertPool, error) { + r, err := os.ReadFile(path) + if err != nil { + return nil, err + } + block, _ := pem.Decode(r) + if block == nil { + return nil, fmt.Errorf("no PEM found in the file %s", path) + } + caRoot, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certPool := x509.NewCertPool() + certPool.AddCert(caRoot) + return certPool, nil +} + +func runACME(listenAddr string, m http.Handler) error { + // If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443. + // Due to docker port mapping this can't be checked programmatically + // TODO: these are placeholders until we add options for each in settings with appropriate warning + enableHTTPChallenge := true + enableTLSALPNChallenge := true + altHTTPPort := 0 + altTLSALPNPort := 0 + + if p, err := strconv.Atoi(setting.PortToRedirect); err == nil { + altHTTPPort = p + } + if p, err := strconv.Atoi(setting.HTTPPort); err == nil { + altTLSALPNPort = p + } + + magic := certmagic.NewDefault() + magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} + // Try to use private CA root if provided, otherwise defaults to system's trust + var certPool *x509.CertPool + if setting.AcmeCARoot != "" { + var err error + certPool, err = getCARoot(setting.AcmeCARoot) + if err != nil { + log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err) + } + } + myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{ + CA: setting.AcmeURL, + TrustedRoots: certPool, + Email: setting.AcmeEmail, + Agreed: setting.AcmeTOS, + DisableHTTPChallenge: !enableHTTPChallenge, + DisableTLSALPNChallenge: !enableTLSALPNChallenge, + ListenHost: setting.HTTPAddr, + AltTLSALPNPort: altTLSALPNPort, + AltHTTPPort: altHTTPPort, + }) + + magic.Issuers = []certmagic.Issuer{myACME} + + // this obtains certificates or renews them if necessary + err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{setting.Domain}) + if err != nil { + return err + } + + tlsConfig := magic.TLSConfig() + tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") + + if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 { + tlsConfig.MinVersion = version + } + if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 { + tlsConfig.MaxVersion = version + } + + // Set curve preferences + if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 { + tlsConfig.CurvePreferences = curves + } + + // Set cipher suites + if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 { + tlsConfig.CipherSuites = ciphers + } + + if enableHTTPChallenge { + go func() { + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true) + defer finished() + + log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) + // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) + err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) + if err != nil { + log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) + } + }() + } + + return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m) +} + +func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(w, "Use HTTPS", http.StatusBadRequest) + return + } + // Remove the trailing slash at the end of setting.AppURL, the request + // URI always contains a leading slash, which would result in a double + // slash + target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusTemporaryRedirect) +} diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go new file mode 100644 index 0000000..1618208 --- /dev/null +++ b/cmd/web_graceful.go @@ -0,0 +1,56 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "net" + "net/http" + "net/http/fcgi" + "strings" + + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +func runHTTP(network, listenAddr, name string, m http.Handler) error { + return graceful.HTTPListenAndServe(network, listenAddr, name, m) +} + +// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector +func NoHTTPRedirector() { + graceful.GetManager().InformCleanup() +} + +// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener +// for our main HTTP/HTTPS service +func NoMainListener() { + graceful.GetManager().InformCleanup() +} + +// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener +// for our install HTTP/HTTPS service +func NoInstallListener() { + graceful.GetManager().InformCleanup() +} + +func runFCGI(network, listenAddr, name string, m http.Handler) error { + // This needs to handle stdin as fcgi point + fcgiServer := graceful.NewServer(network, listenAddr, name) + + err := fcgiServer.ListenAndServe(func(listener net.Listener) error { + return fcgi.Serve(listener, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if setting.AppSubURL != "" { + req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL) + } + m.ServeHTTP(resp, req) + })) + }) + if err != nil { + log.Fatal("Failed to start FCGI main server: %v", err) + } + log.Info("FCGI Listener: %s Closed", listenAddr) + return err +} diff --git a/cmd/web_https.go b/cmd/web_https.go new file mode 100644 index 0000000..b0910ca --- /dev/null +++ b/cmd/web_https.go @@ -0,0 +1,192 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "crypto/tls" + "net/http" + "os" + "strings" + + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/klauspost/cpuid/v2" +) + +var tlsVersionStringMap = map[string]uint16{ + "": tls.VersionTLS12, // Default to tls.VersionTLS12 + "tlsv1.0": tls.VersionTLS10, + "tlsv1.1": tls.VersionTLS11, + "tlsv1.2": tls.VersionTLS12, + "tlsv1.3": tls.VersionTLS13, +} + +func toTLSVersion(version string) uint16 { + tlsVersion, ok := tlsVersionStringMap[strings.TrimSpace(strings.ToLower(version))] + if !ok { + log.Warn("Unknown tls version: %s", version) + return 0 + } + return tlsVersion +} + +var curveStringMap = map[string]tls.CurveID{ + "x25519": tls.X25519, + "p256": tls.CurveP256, + "p384": tls.CurveP384, + "p521": tls.CurveP521, +} + +func toCurvePreferences(preferences []string) []tls.CurveID { + ids := make([]tls.CurveID, 0, len(preferences)) + for _, pref := range preferences { + id, ok := curveStringMap[strings.TrimSpace(strings.ToLower(pref))] + if !ok { + log.Warn("Unknown curve: %s", pref) + } + if id != 0 { + ids = append(ids, id) + } + } + return ids +} + +var cipherStringMap = map[string]uint16{ + "rsa_with_rc4_128_sha": tls.TLS_RSA_WITH_RC4_128_SHA, + "rsa_with_3des_ede_cbc_sha": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + "rsa_with_aes_128_cbc_sha": tls.TLS_RSA_WITH_AES_128_CBC_SHA, + "rsa_with_aes_256_cbc_sha": tls.TLS_RSA_WITH_AES_256_CBC_SHA, + "rsa_with_aes_128_cbc_sha256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + "rsa_with_aes_128_gcm_sha256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + "rsa_with_aes_256_gcm_sha384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + "ecdhe_ecdsa_with_rc4_128_sha": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "ecdhe_ecdsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "ecdhe_ecdsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "ecdhe_rsa_with_rc4_128_sha": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "ecdhe_rsa_with_3des_ede_cbc_sha": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "ecdhe_rsa_with_aes_128_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "ecdhe_rsa_with_aes_256_cbc_sha": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "ecdhe_ecdsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + "ecdhe_rsa_with_aes_128_cbc_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + "ecdhe_rsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + "ecdhe_ecdsa_with_aes_128_gcm_sha256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + "ecdhe_rsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + "ecdhe_ecdsa_with_aes_256_gcm_sha384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + "ecdhe_rsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + "ecdhe_ecdsa_with_chacha20_poly1305_sha256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + "ecdhe_rsa_with_chacha20_poly1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + "ecdhe_ecdsa_with_chacha20_poly1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + "aes_128_gcm_sha256": tls.TLS_AES_128_GCM_SHA256, + "aes_256_gcm_sha384": tls.TLS_AES_256_GCM_SHA384, + "chacha20_poly1305_sha256": tls.TLS_CHACHA20_POLY1305_SHA256, +} + +func toTLSCiphers(cipherStrings []string) []uint16 { + ciphers := make([]uint16, 0, len(cipherStrings)) + for _, cipherString := range cipherStrings { + cipher, ok := cipherStringMap[strings.TrimSpace(strings.ToLower(cipherString))] + if !ok { + log.Warn("Unknown cipher: %s", cipherString) + } + if cipher != 0 { + ciphers = append(ciphers, cipher) + } + } + + return ciphers +} + +// defaultCiphers uses hardware support to check if AES is specifically +// supported by the CPU. +// +// If AES is supported AES ciphers will be preferred over ChaCha based ciphers +// (This code is directly inspired by the certmagic code.) +func defaultCiphers() []uint16 { + if cpuid.CPU.Supports(cpuid.AESNI) { + return defaultCiphersAESfirst + } + return defaultCiphersChaChaFirst +} + +var ( + defaultCiphersAES = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + } + + defaultCiphersChaCha = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + } + + defaultCiphersAESfirst = append(defaultCiphersAES, defaultCiphersChaCha...) + defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...) +) + +// runHTTPs listens on the provided network address and then calls +// Serve to handle requests on incoming TLS connections. +// +// Filenames containing a certificate and matching private key for the server must +// be provided. If the certificate is signed by a certificate authority, the +// certFile should be the concatenation of the server's certificate followed by the +// CA's certificate. +func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error { + tlsConfig := &tls.Config{} + if tlsConfig.NextProtos == nil { + tlsConfig.NextProtos = []string{"h2", "http/1.1"} + } + + if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 { + tlsConfig.MinVersion = version + } + if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 { + tlsConfig.MaxVersion = version + } + + // Set curve preferences + tlsConfig.CurvePreferences = []tls.CurveID{ + tls.X25519, + tls.CurveP256, + } + if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 { + tlsConfig.CurvePreferences = curves + } + + // Set cipher suites + tlsConfig.CipherSuites = defaultCiphers() + if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 { + tlsConfig.CipherSuites = ciphers + } + + tlsConfig.Certificates = make([]tls.Certificate, 1) + + certPEMBlock, err := os.ReadFile(certFile) + if err != nil { + log.Error("Failed to load https cert file %s for %s:%s: %v", certFile, network, listenAddr, err) + return err + } + + keyPEMBlock, err := os.ReadFile(keyFile) + if err != nil { + log.Error("Failed to load https key file %s for %s:%s: %v", keyFile, network, listenAddr, err) + return err + } + + tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err != nil { + log.Error("Failed to create certificate from cert file %s and key file %s for %s:%s: %v", certFile, keyFile, network, listenAddr, err) + return err + } + + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) +} + +func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error { + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) +} diff --git a/main.go b/main.go index 4f70976..08ae1be 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/cmd" "code.gitea.io/gitea/modules/setting" + hat_cmd "code.gitlink.org.cn/Gitlink/gitea_hat.git/cmd" + "github.com/urfave/cli" ) @@ -40,7 +42,7 @@ func main() { arguments - which can alternatively be run by running the subcommand web.` app.Version = Version + formatBuiltWith() app.Commands = []cli.Command{ - cmd.CmdWeb, + hat_cmd.CmdWeb, cmd.CmdServ, cmd.CmdHook, cmd.CmdDump, diff --git a/routers/hat/hat.go b/routers/hat/hat.go new file mode 100644 index 0000000..dfcb82a --- /dev/null +++ b/routers/hat/hat.go @@ -0,0 +1,77 @@ +package hat + +import ( + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/misc" + "code.gitea.io/gitea/services/auth" + "github.com/go-chi/cors" +) + +func Routers() *web.Route { + m := web.NewRoute() + + m.Use(securityHeaders()) + if setting.CORSConfig.Enabled { + m.Use(cors.Handler(cors.Options{ + // Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option + AllowedOrigins: setting.CORSConfig.AllowDomain, + // setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option + AllowedMethods: setting.CORSConfig.Methods, + AllowCredentials: setting.CORSConfig.AllowCredentials, + AllowedHeaders: []string{"Authorization", "X-Gitea-OTP"}, + MaxAge: int(setting.CORSConfig.MaxAge.Seconds()), + })) + } + m.Use(context.APIContexter()) + + group := buildAuthGroup() + if err := group.Init(); err != nil { + log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err) + } + + // Get user from session if logged in. + m.Use(context.APIAuth(group)) + + m.Use(context.ToggleAPI(&context.ToggleOptions{ + SignInRequired: setting.Service.RequireSignInView, + })) + + m.Use(context.ToggleAPI(&context.ToggleOptions{ + SignInRequired: setting.Service.RequireSignInView, + })) + + m.Group("", func() { + m.Get("/version", misc.Version) + }) + + return m +} + +func securityHeaders() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers + // http://stackoverflow.com/a/3146618/244009 + resp.Header().Set("x-content-type-options", "nosniff") + next.ServeHTTP(resp, req) + }) + } +} + +func buildAuthGroup() *auth.Group { + group := auth.NewGroup( + &auth.OAuth2{}, + &auth.HTTPSign{}, + &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API + ) + if setting.Service.EnableReverseProxyAuth { + group.Add(&auth.ReverseProxy{}) + } + + return group +} diff --git a/routers/hat/repo/tag.go b/routers/hat/repo/tag.go new file mode 100644 index 0000000..7427ce6 --- /dev/null +++ b/routers/hat/repo/tag.go @@ -0,0 +1,58 @@ +package repo + +import ( + "net/http" + "strings" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" +) + +func TagNameSet(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/tag_name_set repository repoTagNameSet + // --- + // summary: List a repository's tag name*** + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: name + // in: query + // description: name of the tag + // type: string + // responses: + // "200": + // "$ref": "#/responses/BranchNameSet" + + searchName := ctx.Params("name") + + tags, err := ctx.Repo.GitRepo.GetTags(0, 0) + if err != nil { + ctx.ServerError("GetTags", err) + return + } + + if searchName == "" { + ctx.JSON(http.StatusOK, tags) + } else { + var tagNameSet []string + for _, tag := range tags { + + log.Info("tag is \n", tag) + if strings.Contains(tag, searchName) { + tagNameSet = append(tagNameSet, tag) + } + } + ctx.JSON(http.StatusOK, tagNameSet) + } + +} diff --git a/routers/init.go b/routers/init.go new file mode 100644 index 0000000..558db73 --- /dev/null +++ b/routers/init.go @@ -0,0 +1,12 @@ +package routers + +import ( + "code.gitea.io/gitea/modules/web" + api_hat "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat" +) + +func InitHatRouters(e *web.Route) *web.Route { + + e.Mount("/api/hat", api_hat.Routers()) + return e +}