Skip to content

Commit a92957a

Browse files
committed
Move from gracehttp to our own service to add graceful ssh
1 parent 356de34 commit a92957a

32 files changed

+820
-1910
lines changed

cmd/web.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,13 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
7575
}
7676
go func() {
7777
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
78-
var err = http.ListenAndServe(setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
78+
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
79+
var err = runHTTP(setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
7980
if err != nil {
8081
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
8182
}
8283
}()
83-
server := &http.Server{
84-
Addr: listenAddr,
85-
Handler: m,
86-
TLSConfig: certManager.TLSConfig(),
87-
}
88-
return server.ListenAndServeTLS("", "")
84+
return runHTTPSWithTLSConfig(listenAddr, certManager.TLSConfig(), context2.ClearHandler(m))
8985
}
9086

9187
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
@@ -101,12 +97,21 @@ func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
10197
}
10298

10399
func runWeb(ctx *cli.Context) error {
100+
if os.Getppid() > 1 && len(os.Getenv("LISTEN_FDS")) > 0 {
101+
log.Info("Restarting Gitea on PID: %d from parent PID: %d", os.Getpid(), os.Getppid())
102+
} else {
103+
log.Info("Starting Gitea on PID: %d", os.Getpid())
104+
}
105+
106+
// Set pid file setting
104107
if ctx.IsSet("pid") {
105108
setting.CustomPID = ctx.String("pid")
106109
}
107110

111+
// Perform global initialization
108112
routers.GlobalInit()
109113

114+
// Set up Macaron
110115
m := routes.NewMacaron()
111116
routes.RegisterRoutes(m)
112117

@@ -207,8 +212,12 @@ func runWeb(ctx *cli.Context) error {
207212
}
208213

209214
if err != nil {
210-
log.Fatal("Failed to start server: %v", err)
215+
if strings.Contains(err.Error(), "use of closed") {
216+
log.Info("HTTP Listener: %s Closed", listenAddr)
217+
log.Close()
218+
} else {
219+
log.Fatal("Failed to start server: %v", err)
220+
}
211221
}
212-
213222
return nil
214223
}

cmd/web_graceful.go

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,17 @@ import (
1010
"crypto/tls"
1111
"net/http"
1212

13-
"code.gitea.io/gitea/modules/log"
14-
15-
"github.com/facebookgo/grace/gracehttp"
13+
"code.gitea.io/gitea/modules/graceful"
1614
)
1715

1816
func runHTTP(listenAddr string, m http.Handler) error {
19-
return gracehttp.Serve(&http.Server{
20-
Addr: listenAddr,
21-
Handler: m,
22-
})
17+
return graceful.HTTPListenAndServe("tcp", listenAddr, m)
2318
}
2419

2520
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
26-
config := &tls.Config{
27-
MinVersion: tls.VersionTLS10,
28-
}
29-
if config.NextProtos == nil {
30-
config.NextProtos = []string{"http/1.1"}
31-
}
32-
33-
config.Certificates = make([]tls.Certificate, 1)
34-
var err error
35-
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
36-
if err != nil {
37-
log.Fatal("Failed to load https cert file %s: %v", listenAddr, err)
38-
}
21+
return graceful.HTTPListenAndServeTLS("tcp", listenAddr, certFile, keyFile, m)
22+
}
3923

40-
return gracehttp.Serve(&http.Server{
41-
Addr: listenAddr,
42-
Handler: m,
43-
TLSConfig: config,
44-
})
24+
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
25+
return graceful.HTTPListenAndServeTLSConfig("tcp", listenAddr, tlsConfig, m)
4526
}

cmd/web_windows.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package cmd
88

99
import (
10+
"crypto/tls"
1011
"net/http"
1112
)
1213

@@ -17,3 +18,12 @@ func runHTTP(listenAddr string, m http.Handler) error {
1718
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
1819
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m)
1920
}
21+
22+
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
23+
server := &http.Server{
24+
Addr: listenAddr,
25+
Handler: m,
26+
TLSConfig: tlsConfig,
27+
}
28+
return server.ListenAndServeTLS("", "")
29+
}

go.mod

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,8 @@ require (
3232
github.com/emirpasic/gods v1.12.0
3333
github.com/etcd-io/bbolt v1.3.2 // indirect
3434
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a
35-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
3635
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
37-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
38-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f
39-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2 // indirect
4036
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
41-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 // indirect
4237
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
4338
github.com/gliderlabs/ssh v0.2.2
4439
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd // indirect

go.sum

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,10 @@ github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0=
7878
github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
7979
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a h1:M1bRpaZAn4GSsqu3hdK2R8H0AH9O6vqCTCbm2oAFGfE=
8080
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a/go.mod h1:MkKY/CB98aVE4VxO63X5vTQKUgcn+3XP15LMASe3lYs=
81-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
82-
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
8381
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
8482
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
85-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 h1:wWke/RUCl7VRjQhwPlR/v0glZXNYzBHdNUzf/Am2Nmg=
86-
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9/go.mod h1:uPmAp6Sws4L7+Q/OokbWDAK1ibXYhB3PXFP1kol5hPg=
87-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f h1:0mlfEUWnUDVZnqWEVHGerL5bKYDKMEmT/Qk/W/3nGuo=
88-
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f/go.mod h1:KigFdumBXUPSwzLDbeuzyt0elrL7+CP7TKuhrhT4bcU=
89-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2 h1:3Zvf9wRhl1cOhckN1oRGWPOkIhOketmEcrQ4TeFAoR4=
90-
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2/go.mod h1:TUV/fX3XrTtBQb5+ttSUJzcFgLNpILONFTKmBuk5RSw=
9183
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
9284
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
93-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 h1:0YtRCqIZs2+Tz49QuH6cJVw/IFqzo39gEqZ0iYLxD2M=
94-
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4/go.mod h1:vsJz7uE339KUCpBXx3JAJzSRH7Uk4iGGyJzR529qDIA=
9585
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
9686
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
9787
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=

modules/graceful/net.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package graceful
6+
7+
import (
8+
"fmt"
9+
"net"
10+
"os"
11+
"os/exec"
12+
"strconv"
13+
"strings"
14+
"sync"
15+
)
16+
17+
const (
18+
listenFDs = "LISTEN_FDS"
19+
startFD = 3
20+
)
21+
22+
// In order to keep the working directory the same as when we started we record
23+
// it at startup.
24+
var originalWD, _ = os.Getwd()
25+
26+
var (
27+
once = sync.Once{}
28+
mutex = sync.Mutex{}
29+
30+
providedListeners = []net.Listener{}
31+
activeListeners = []net.Listener{}
32+
)
33+
34+
func getProvidedFDs() (savedErr error) {
35+
once.Do(func() {
36+
mutex.Lock()
37+
defer mutex.Unlock()
38+
numFDs := os.Getenv(listenFDs)
39+
if numFDs == "" {
40+
return
41+
}
42+
n, err := strconv.Atoi(numFDs)
43+
if err != nil {
44+
savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDs, numFDs, err)
45+
return
46+
}
47+
for i := startFD; i < n+startFD; i++ {
48+
file := os.NewFile(uintptr(i), "listener")
49+
50+
l, err := net.FileListener(file)
51+
if err == nil {
52+
if err = file.Close(); err != nil {
53+
savedErr = fmt.Errorf("error closing provided socket fd %d: %s", i, err)
54+
return
55+
}
56+
providedListeners = append(providedListeners, l)
57+
continue
58+
}
59+
// If needed we can handle packetconns here.
60+
savedErr = fmt.Errorf("Error getting provided socket fd %d: %v", i, err)
61+
return
62+
}
63+
})
64+
return savedErr
65+
}
66+
67+
// GetListener obtains a listener for the local network address. The network must be
68+
// a stream-oriented network: "tcp", "tcp4", "tcp6", "unix" or "unixpacket". It
69+
// returns an provided net.Listener for the matching network and address, or
70+
// creates a new one using net.Listen.
71+
func GetListener(network, address string) (net.Listener, error) {
72+
switch network {
73+
default:
74+
return nil, net.UnknownNetworkError(network)
75+
case "tcp", "tcp4", "tcp6":
76+
tcpAddr, err := net.ResolveTCPAddr(network, address)
77+
if err != nil {
78+
return nil, err
79+
}
80+
return GetListenerTCP(network, tcpAddr)
81+
case "unix", "unixpacket", "invalid_unix_net_for_test":
82+
unixAddr, err := net.ResolveUnixAddr(network, address)
83+
if err != nil {
84+
return nil, err
85+
}
86+
return GetListenerUnix(network, unixAddr)
87+
}
88+
}
89+
90+
// GetListenerTCP announces on the local network address. The network must be:
91+
// "tcp", "tcp4" or "tcp6". It returns a provided net.Listener for the
92+
// matching network and address, or creates a new one using net.ListenTCP.
93+
func GetListenerTCP(network string, address *net.TCPAddr) (*net.TCPListener, error) {
94+
if err := getProvidedFDs(); err != nil {
95+
return nil, err
96+
}
97+
98+
mutex.Lock()
99+
defer mutex.Unlock()
100+
101+
// look for a provided listener
102+
for i, l := range providedListeners {
103+
if isSameAddr(l.Addr(), address) {
104+
providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
105+
106+
activeListeners = append(activeListeners, l)
107+
return l.(*net.TCPListener), nil
108+
}
109+
}
110+
111+
// make a fresh listener
112+
l, err := net.ListenTCP(network, address)
113+
if err != nil {
114+
return nil, err
115+
}
116+
activeListeners = append(activeListeners, l)
117+
return l, nil
118+
}
119+
120+
// GetListenerUnix announces on the local network address. The network must be:
121+
// "unix" or "unixpacket". It returns a provided net.Listener for the
122+
// matching network and address, or creates a new one using net.ListenUnix.
123+
func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener, error) {
124+
if err := getProvidedFDs(); err != nil {
125+
return nil, err
126+
}
127+
128+
mutex.Lock()
129+
defer mutex.Unlock()
130+
131+
// look for a provided listener
132+
for i, l := range providedListeners {
133+
if isSameAddr(l.Addr(), address) {
134+
providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
135+
activeListeners = append(activeListeners, l)
136+
return l.(*net.UnixListener), nil
137+
}
138+
}
139+
140+
// make a fresh listener
141+
l, err := net.ListenUnix(network, address)
142+
if err != nil {
143+
return nil, err
144+
}
145+
activeListeners = append(activeListeners, l)
146+
return l, nil
147+
}
148+
149+
func isSameAddr(a1, a2 net.Addr) bool {
150+
// If the addresses are not on the same network fail.
151+
if a1.Network() != a2.Network() {
152+
return false
153+
}
154+
155+
// If the two addresses have the same string representation they're equal
156+
a1s := a1.String()
157+
a2s := a2.String()
158+
if a1s == a2s {
159+
return true
160+
}
161+
162+
// This allows for ipv6 vs ipv4 local addresses to compare as equal. This
163+
// scenario is common when listening on localhost.
164+
const ipv6prefix = "[::]"
165+
a1s = strings.TrimPrefix(a1s, ipv6prefix)
166+
a2s = strings.TrimPrefix(a2s, ipv6prefix)
167+
const ipv4prefix = "0.0.0.0"
168+
a1s = strings.TrimPrefix(a1s, ipv4prefix)
169+
a2s = strings.TrimPrefix(a2s, ipv4prefix)
170+
return a1s == a2s
171+
}
172+
173+
func getActiveListeners() []net.Listener {
174+
mutex.Lock()
175+
defer mutex.Unlock()
176+
listeners := make([]net.Listener, len(activeListeners))
177+
copy(listeners, activeListeners)
178+
return listeners
179+
}
180+
181+
// RestartProcess starts a new process passing it the active listeners. It
182+
// doesn't fork, but starts a new process using the same environment and
183+
// arguments as when it was originally started. This allows for a newly
184+
// deployed binary to be started. It returns the pid of the newly started
185+
// process when successful.
186+
func RestartProcess() (int, error) {
187+
listeners := getActiveListeners()
188+
189+
// Extract the fds from the listeners.
190+
files := make([]*os.File, len(listeners))
191+
for i, l := range listeners {
192+
var err error
193+
// Now, all our listeners actually have File() functions so instead of
194+
// individually casting we just use a hacky interface
195+
files[i], err = l.(filer).File()
196+
if err != nil {
197+
return 0, err
198+
}
199+
// Remember to close these at the end.
200+
defer files[i].Close()
201+
}
202+
203+
// Use the original binary location. This works with symlinks such that if
204+
// the file it points to has been changed we will use the updated symlink.
205+
argv0, err := exec.LookPath(os.Args[0])
206+
if err != nil {
207+
return 0, err
208+
}
209+
210+
// Pass on the environment and replace the old count key with the new one.
211+
var env []string
212+
for _, v := range os.Environ() {
213+
if !strings.HasPrefix(v, listenFDs+"=") {
214+
env = append(env, v)
215+
}
216+
}
217+
env = append(env, fmt.Sprintf("%s=%d", listenFDs, len(listeners)))
218+
219+
allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
220+
process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
221+
Dir: originalWD,
222+
Env: env,
223+
Files: allFiles,
224+
})
225+
if err != nil {
226+
return 0, err
227+
}
228+
return process.Pid, nil
229+
}
230+
231+
type filer interface {
232+
File() (*os.File, error)
233+
}

0 commit comments

Comments
 (0)