lemval
2012-01-31 1c30dad2115fc513791d8a5b292ad0f7d7b85749
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
5fe7df 16 package com.gitblit;
JM 17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.io.OutputStream;
23 import java.net.InetAddress;
24 import java.net.ServerSocket;
25 import java.net.Socket;
26 import java.net.URL;
27 import java.net.UnknownHostException;
28 import java.security.ProtectionDomain;
87cc1e 29 import java.text.MessageFormat;
5fe7df 30 import java.util.ArrayList;
JM 31 import java.util.List;
32
33 import org.eclipse.jetty.server.Connector;
34 import org.eclipse.jetty.server.Server;
35 import org.eclipse.jetty.server.bio.SocketConnector;
36 import org.eclipse.jetty.server.nio.SelectChannelConnector;
a4d249 37 import org.eclipse.jetty.server.session.HashSessionManager;
5fe7df 38 import org.eclipse.jetty.server.ssl.SslConnector;
JM 39 import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
40 import org.eclipse.jetty.server.ssl.SslSocketConnector;
41 import org.eclipse.jetty.util.thread.QueuedThreadPool;
42 import org.eclipse.jetty.webapp.WebAppContext;
892570 43 import org.eclipse.jgit.util.FileUtils;
2a7306 44 import org.slf4j.Logger;
JM 45 import org.slf4j.LoggerFactory;
5fe7df 46
JM 47 import com.beust.jcommander.JCommander;
48 import com.beust.jcommander.Parameter;
49 import com.beust.jcommander.ParameterException;
50 import com.beust.jcommander.Parameters;
87cc1e 51 import com.gitblit.utils.StringUtils;
5fe7df 52
892570 53 /**
JM 54  * GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts
55  * and stops an instance of Jetty that is configured from a combination of the
56  * gitblit.properties file and command line parameters. JCommander is used to
57  * simplify command line parameter processing. This class also automatically
58  * generates a self-signed certificate for localhost, if the keystore does not
59  * already exist.
60  * 
61  * @author James Moger
62  * 
63  */
5fe7df 64 public class GitBlitServer {
JM 65
2a7306 66     private static Logger logger;
5fe7df 67
a2709d 68     public static void main(String... args) {
5fe7df 69         Params params = new Params();
JM 70         JCommander jc = new JCommander(params);
71         try {
72             jc.parse(args);
2a7306 73             if (params.help) {
5fe7df 74                 usage(jc, null);
2a7306 75             }
5fe7df 76         } catch (ParameterException t) {
JM 77             usage(jc, t);
78         }
79
2a7306 80         if (params.stop) {
5fe7df 81             stop(params);
2a7306 82         } else {
5fe7df 83             start(params);
2a7306 84         }
5fe7df 85     }
JM 86
892570 87     /**
JM 88      * Display the command line usage of Gitblit GO.
89      * 
90      * @param jc
91      * @param t
92      */
5fe7df 93     private static void usage(JCommander jc, ParameterException t) {
1f9dae 94         System.out.println(Constants.BORDER);
2a7306 95         System.out.println(Constants.getGitBlitVersion());
1f9dae 96         System.out.println(Constants.BORDER);
5fe7df 97         System.out.println();
JM 98         if (t != null) {
99             System.out.println(t.getMessage());
100             System.out.println();
101         }
102         if (jc != null) {
103             jc.usage();
2a7306 104             System.out
892570 105                     .println("\nExample:\n  java -server -Xmx1024M -jar gitblit.jar --repositoriesFolder c:\\git --httpPort 80 --httpsPort 443");
5fe7df 106         }
JM 107         System.exit(0);
108     }
109
110     /**
892570 111      * Stop Gitblt GO.
5fe7df 112      */
JM 113     public static void stop(Params params) {
114         try {
115             Socket s = new Socket(InetAddress.getByName("127.0.0.1"), params.shutdownPort);
116             OutputStream out = s.getOutputStream();
117             System.out.println("Sending Shutdown Request to " + Constants.NAME);
2a7306 118             out.write("\r\n".getBytes());
5fe7df 119             out.flush();
JM 120             s.close();
121         } catch (UnknownHostException e) {
122             e.printStackTrace();
123         } catch (IOException e) {
124             e.printStackTrace();
125         }
126     }
127
128     /**
892570 129      * Start Gitblit GO.
5fe7df 130      */
JM 131     private static void start(Params params) {
85c2e6 132         FileSettings settings = Params.FILESETTINGS;
7e8873 133         if (!StringUtils.isEmpty(params.settingsfile)) {
JM 134             if (new File(params.settingsfile).exists()) {
135                 settings = new FileSettings(params.settingsfile);                
136             }
137         }
5fe7df 138
2a7306 139         logger = LoggerFactory.getLogger(GitBlitServer.class);
1f9dae 140         logger.info(Constants.BORDER);
2a02f8 141         logger.info("            _____  _  _    _      _  _  _");
JM 142         logger.info("           |  __ \\(_)| |  | |    | |(_)| |");
143         logger.info("           | |  \\/ _ | |_ | |__  | | _ | |_");
144         logger.info("           | | __ | || __|| '_ \\ | || || __|");
145         logger.info("           | |_\\ \\| || |_ | |_) || || || |_");
146         logger.info("            \\____/|_| \\__||_.__/ |_||_| \\__|");
147         int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2;
148         StringBuilder sb = new StringBuilder();
149         while (spacing > 0) {
150             spacing--;
151             sb.append(' ');
152         }
153         logger.info(sb.toString() + Constants.getGitBlitVersion());
154         logger.info("");
1f9dae 155         logger.info(Constants.BORDER);
5fe7df 156
JM 157         String osname = System.getProperty("os.name");
158         String osversion = System.getProperty("os.version");
159         logger.info("Running on " + osname + " (" + osversion + ")");
160
161         List<Connector> connectors = new ArrayList<Connector>();
892570 162
JM 163         // conditionally configure the http connector
5fe7df 164         if (params.port > 0) {
JM 165             Connector httpConnector = createConnector(params.useNIO, params.port);
1f9dae 166             String bindInterface = settings.getString(Keys.server.httpBindInterface, null);
87cc1e 167             if (!StringUtils.isEmpty(bindInterface)) {
f28063 168                 logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
2a7306 169                         params.port, bindInterface));
87cc1e 170                 httpConnector.setHost(bindInterface);
JM 171             }
18422e 172             if (params.port < 1024 && !isWindows()) {
JM 173                 logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
174             }
5fe7df 175             connectors.add(httpConnector);
JM 176         }
7ba0ec 177
892570 178         // conditionally configure the https connector
5fe7df 179         if (params.securePort > 0) {
5b60db 180             File keystore = new File("keystore");
JM 181             if (!keystore.exists()) {
892570 182                 logger.info("Generating self-signed SSL certificate for localhost");
2a7306 183                 MakeCertificate.generateSelfSignedCertificate("localhost", keystore,
JM 184                         params.storePassword);
5b60db 185             }
JM 186             if (keystore.exists()) {
2a7306 187                 Connector secureConnector = createSSLConnector(keystore, params.storePassword,
JM 188                         params.useNIO, params.securePort);
1f9dae 189                 String bindInterface = settings.getString(Keys.server.httpsBindInterface, null);
87cc1e 190                 if (!StringUtils.isEmpty(bindInterface)) {
2a02f8 191                     logger.warn(MessageFormat.format(
JM 192                             "Binding ssl connector on port {0,number,0} to {1}", params.securePort,
193                             bindInterface));
87cc1e 194                     secureConnector.setHost(bindInterface);
JM 195                 }
18422e 196                 if (params.securePort < 1024 && !isWindows()) {
JM 197                     logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
88598b 198                 }
5fe7df 199                 connectors.add(secureConnector);
JM 200             } else {
5b60db 201                 logger.warn("Failed to find or load Keystore?");
5fe7df 202                 logger.warn("SSL connector DISABLED.");
JM 203             }
204         }
205
892570 206         // tempDir is where the embedded Gitblit web application is expanded and
JM 207         // where Jetty creates any necessary temporary files
5fe7df 208         File tempDir = new File(params.temp);
2a7306 209         if (tempDir.exists()) {
892570 210             try {
JM 211                 FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
212             } catch (IOException x) {
213                 logger.warn("Failed to delete temp dir " + tempDir.getAbsolutePath(), x);
2a7306 214             }
JM 215         }
216         if (!tempDir.mkdirs()) {
217             logger.warn("Failed to create temp dir " + tempDir.getAbsolutePath());
218         }
5fe7df 219
JM 220         Server server = new Server();
221         server.setStopAtShutdown(true);
222         server.setConnectors(connectors.toArray(new Connector[connectors.size()]));
223
224         // Get the execution path of this class
225         // We use this to set the WAR path.
226         ProtectionDomain protectionDomain = GitBlitServer.class.getProtectionDomain();
227         URL location = protectionDomain.getCodeSource().getLocation();
228
229         // Root WebApp Context
230         WebAppContext rootContext = new WebAppContext();
5d253a 231         rootContext.setContextPath(settings.getString(Keys.server.contextPath, "/"));
5fe7df 232         rootContext.setServer(server);
JM 233         rootContext.setWar(location.toExternalForm());
234         rootContext.setTempDirectory(tempDir);
2a7306 235
JM 236         // Set cookies HttpOnly so they are not accessible to JavaScript engines
a4d249 237         HashSessionManager sessionManager = new HashSessionManager();
JM 238         sessionManager.setHttpOnly(true);
239         // Use secure cookies if only serving https
240         sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);
241         rootContext.getSessionHandler().setSessionManager(sessionManager);
8c9a20 242
85c2e6 243         // Ensure there is a defined User Service
JM 244         String realmUsers = params.userService;
8c9a20 245         if (StringUtils.isEmpty(realmUsers)) {
85c2e6 246             logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.userService));
8c9a20 247             return;
JM 248         }
85c2e6 249
892570 250         // Override settings from the command-line
85c2e6 251         settings.overrideSetting(Keys.realm.userService, params.userService);
0fe70c 252         settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);
db653a 253
5fe7df 254         // Set the server's contexts
8c9a20 255         server.setHandler(rootContext);
87cc1e 256
JM 257         // Setup the GitBlit context
258         GitBlit gitblit = GitBlit.self();
f6740d 259         gitblit.configureContext(settings, true);
87cc1e 260         rootContext.addEventListener(gitblit);
5fe7df 261
JM 262         try {
892570 263             // start the shutdown monitor
5fe7df 264             if (params.shutdownPort > 0) {
JM 265                 Thread shutdownMonitor = new ShutdownMonitorThread(server, params);
266                 shutdownMonitor.start();
267             }
892570 268
JM 269             // start Jetty
5fe7df 270             server.start();
JM 271             server.join();
272         } catch (Exception e) {
273             e.printStackTrace();
274             System.exit(100);
275         }
276     }
277
892570 278     /**
JM 279      * Creates an http connector.
280      * 
281      * @param useNIO
282      * @param port
18422e 283      * @return an http connector
892570 284      */
5fe7df 285     private static Connector createConnector(boolean useNIO, int port) {
JM 286         Connector connector;
287         if (useNIO) {
288             logger.info("Setting up NIO SelectChannelConnector on port " + port);
289             SelectChannelConnector nioconn = new SelectChannelConnector();
290             nioconn.setSoLingerTime(-1);
291             nioconn.setThreadPool(new QueuedThreadPool(20));
292             connector = nioconn;
293         } else {
294             logger.info("Setting up SocketConnector on port " + port);
295             SocketConnector sockconn = new SocketConnector();
296             connector = sockconn;
297         }
298
299         connector.setPort(port);
300         connector.setMaxIdleTime(30000);
18422e 301         if (port < 1024 && !isWindows()) {
JM 302             logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
303         }
5fe7df 304         return connector;
JM 305     }
306
892570 307     /**
JM 308      * Creates an https connector.
309      * 
c7ebb2 310      * SSL renegotiation will be enabled if the JVM is 1.6.0_22 or later.
JM 311      * oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html
312      * 
892570 313      * @param keystore
JM 314      * @param password
315      * @param useNIO
316      * @param port
317      * @return an https connector
318      */
2a7306 319     private static Connector createSSLConnector(File keystore, String password, boolean useNIO,
JM 320             int port) {
5fe7df 321         SslConnector connector;
JM 322         if (useNIO) {
323             logger.info("Setting up NIO SslSelectChannelConnector on port " + port);
324             SslSelectChannelConnector ssl = new SslSelectChannelConnector();
325             ssl.setSoLingerTime(-1);
2a7306 326             ssl.setThreadPool(new QueuedThreadPool(20));
5fe7df 327             connector = ssl;
JM 328         } else {
329             logger.info("Setting up NIO SslSocketConnector on port " + port);
330             SslSocketConnector ssl = new SslSocketConnector();
331             connector = ssl;
332         }
c7ebb2 333         // disable renegotiation unless this is a patched JVM
JM 334         boolean allowRenegotiation = false;
335         String v = System.getProperty("java.version");
336         if (v.startsWith("1.7")) {
337             allowRenegotiation = true;
338         } else if (v.startsWith("1.6")) {
339             // 1.6.0_22 was first release with RFC-5746 implemented fix.
340             if (v.indexOf('_') > -1) {
341                 String b = v.substring(v.indexOf('_') + 1);
342                 if (Integer.parseInt(b) >= 22) {
343                     allowRenegotiation = true;
344                 }
345             }
346         }
347         if (allowRenegotiation) {
348             logger.info("   allowing SSL renegotiation on Java " + v);
f28063 349             connector.setAllowRenegotiate(allowRenegotiation);
2a02f8 350         }
5b60db 351         connector.setKeystore(keystore.getAbsolutePath());
5fe7df 352         connector.setPassword(password);
JM 353         connector.setPort(port);
354         connector.setMaxIdleTime(30000);
355         return connector;
356     }
88598b 357
18422e 358     /**
JM 359      * Tests to see if the operating system is Windows.
360      * 
361      * @return true if this is a windows machine
362      */
363     private static boolean isWindows() {
364         return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1;
365     }
2a7306 366
5fe7df 367     /**
892570 368      * The ShutdownMonitorThread opens a socket on a specified port and waits
JM 369      * for an incoming connection. When that connection is accepted a shutdown
370      * message is issued to the running Jetty server.
5fe7df 371      * 
892570 372      * @author James Moger
JM 373      * 
5fe7df 374      */
JM 375     private static class ShutdownMonitorThread extends Thread {
376
377         private final ServerSocket socket;
378
379         private final Server server;
db653a 380
1f9dae 381         private final Logger logger = LoggerFactory.getLogger(ShutdownMonitorThread.class);
5fe7df 382
JM 383         public ShutdownMonitorThread(Server server, Params params) {
384             this.server = server;
385             setDaemon(true);
386             setName(Constants.NAME + " Shutdown Monitor");
387             ServerSocket skt = null;
388             try {
389                 skt = new ServerSocket(params.shutdownPort, 1, InetAddress.getByName("127.0.0.1"));
390             } catch (Exception e) {
2a7306 391                 logger.warn("Could not open shutdown monitor on port " + params.shutdownPort, e);
5fe7df 392             }
JM 393             socket = skt;
394         }
395
396         @Override
397         public void run() {
398             logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
399             Socket accept;
400             try {
401                 accept = socket.accept();
2a7306 402                 BufferedReader reader = new BufferedReader(new InputStreamReader(
JM 403                         accept.getInputStream()));
5fe7df 404                 reader.readLine();
1f9dae 405                 logger.info(Constants.BORDER);
5fe7df 406                 logger.info("Stopping " + Constants.NAME);
1f9dae 407                 logger.info(Constants.BORDER);
5fe7df 408                 server.stop();
JM 409                 server.setStopAtShutdown(false);
410                 accept.close();
411                 socket.close();
412             } catch (Exception e) {
413                 logger.warn("Failed to shutdown Jetty", e);
414             }
415         }
416     }
417
88598b 418     /**
JM 419      * JCommander Parameters class for GitBlitServer.
420      */
5fe7df 421     @Parameters(separators = " ")
JM 422     private static class Params {
db653a 423
28d6b2 424         private static final FileSettings FILESETTINGS = new FileSettings(Constants.PROPERTIES_FILE);
5fe7df 425
JM 426         /*
427          * Server parameters
428          */
429         @Parameter(names = { "-h", "--help" }, description = "Show this help")
430         public Boolean help = false;
431
432         @Parameter(names = { "--stop" }, description = "Stop Server")
433         public Boolean stop = false;
434
85c2e6 435         @Parameter(names = { "--tempFolder" }, description = "Folder for server to extract built-in webapp")
2a7306 436         public String temp = FILESETTINGS.getString(Keys.server.tempFolder, "temp");
5fe7df 437
JM 438         /*
439          * GIT Servlet Parameters
440          */
dfb889 441         @Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
2a7306 442         public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder,
JM 443                 "repos");
5fe7df 444
JM 445         /*
446          * Authentication Parameters
447          */
85c2e6 448         @Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)")
892570 449         public String userService = FILESETTINGS.getString(Keys.realm.userService,
JM 450                 "users.properties");
5fe7df 451
JM 452         /*
453          * JETTY Parameters
454          */
dfb889 455         @Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")
2a7306 456         public Boolean useNIO = FILESETTINGS.getBoolean(Keys.server.useNio, true);
5fe7df 457
dfb889 458         @Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
2a7306 459         public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 80);
5fe7df 460
dfb889 461         @Parameter(names = "--httpsPort", description = "HTTPS port to serve.  (port <= 0 will disable this connector)")
2a7306 462         public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 443);
5fe7df 463
JM 464         @Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")
2a7306 465         public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, "");
5fe7df 466
JM 467         @Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)")
2a7306 468         public Integer shutdownPort = FILESETTINGS.getInteger(Keys.server.shutdownPort, 8081);
5fe7df 469
7e8873 470         /*
JM 471          * Setting overrides
472          */
473         @Parameter(names = { "--settings" }, description = "Path to alternative settings")
474         public String settingsfile;
475
5fe7df 476     }
JM 477 }