Newer
Older
gitbucket_jkp / src / main / java / JettyLauncher.java
  1. import org.eclipse.jetty.http.HttpVersion;
  2. import org.eclipse.jetty.server.Handler;
  3. import org.eclipse.jetty.server.HttpConfiguration;
  4. import org.eclipse.jetty.server.HttpConnectionFactory;
  5. import org.eclipse.jetty.server.SecureRequestCustomizer;
  6. import org.eclipse.jetty.server.Server;
  7. import org.eclipse.jetty.server.ServerConnector;
  8. import org.eclipse.jetty.server.SslConnectionFactory;
  9. import org.eclipse.jetty.server.handler.HandlerList;
  10. import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
  11. import org.eclipse.jetty.server.handler.StatisticsHandler;
  12. import org.eclipse.jetty.server.session.DefaultSessionCache;
  13. import org.eclipse.jetty.server.session.FileSessionDataStore;
  14. import org.eclipse.jetty.server.session.SessionCache;
  15. import org.eclipse.jetty.server.session.SessionHandler;
  16. import org.eclipse.jetty.util.ssl.SslContextFactory;
  17. import org.eclipse.jetty.webapp.WebAppContext;
  18.  
  19. import java.io.File;
  20. import java.net.InetAddress;
  21. import java.net.URL;
  22. import java.security.ProtectionDomain;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Set;
  26. import java.util.function.Function;
  27. import java.util.stream.Stream;
  28.  
  29. import static java.util.function.Function.identity;
  30. import static java.util.stream.Collectors.toSet;
  31.  
  32. public class JettyLauncher {
  33.  
  34. private interface Defaults {
  35.  
  36. String CONNECTORS = "http";
  37. String HOST = "0.0.0.0";
  38.  
  39. int HTTP_PORT = 8080;
  40. int HTTPS_PORT = 8443;
  41.  
  42. boolean REDIRECT_HTTPS = false;
  43. }
  44.  
  45. private interface Connectors {
  46.  
  47. String HTTP = "http";
  48. String HTTPS = "https";
  49. }
  50.  
  51. public static void main(String[] args) throws Exception {
  52. System.setProperty("java.awt.headless", "true");
  53.  
  54. String connectors = getEnvironmentVariable("gitbucket.connectors");
  55. String host = getEnvironmentVariable("gitbucket.host");
  56. String port = getEnvironmentVariable("gitbucket.port");
  57. String securePort = getEnvironmentVariable("gitbucket.securePort");
  58. String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
  59. String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword");
  60. String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword");
  61. String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
  62. String contextPath = getEnvironmentVariable("gitbucket.prefix");
  63. String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
  64. String jettyIdleTimeout = getEnvironmentVariable("gitbucket.jettyIdleTimeout");
  65. boolean saveSessions = false;
  66.  
  67. for(String arg: args) {
  68. if (arg.equals("--save_sessions")) {
  69. saveSessions = true;
  70. }
  71. if (arg.equals("--disable_news_feed")) {
  72. System.setProperty("gitbucket.disableNewsFeed", "true");
  73. }
  74. if (arg.equals("--disable_cache")) {
  75. System.setProperty("gitbucket.disableCache", "true");
  76. }
  77. if(arg.startsWith("--") && arg.contains("=")) {
  78. String[] dim = arg.split("=", 2);
  79. if(dim.length == 2) {
  80. switch (dim[0]) {
  81. case "--connectors":
  82. connectors = dim[1];
  83. break;
  84. case "--host":
  85. host = dim[1];
  86. break;
  87. case "--port":
  88. port = dim[1];
  89. break;
  90. case "--secure_port":
  91. securePort = dim[1];
  92. break;
  93. case "--key_store_path":
  94. keyStorePath = dim[1];
  95. break;
  96. case "--key_store_password":
  97. keyStorePassword = dim[1];
  98. break;
  99. case "--key_manager_password":
  100. keyManagerPassword = dim[1];
  101. break;
  102. case "--redirect_https":
  103. redirectHttps = dim[1];
  104. break;
  105. case "--prefix":
  106. contextPath = dim[1];
  107. break;
  108. case "--gitbucket.home":
  109. System.setProperty("gitbucket.home", dim[1]);
  110. break;
  111. case "--temp_dir":
  112. tmpDirPath = dim[1];
  113. break;
  114. case "--plugin_dir":
  115. System.setProperty("gitbucket.pluginDir", dim[1]);
  116. break;
  117. case "--jetty_idle_timeout":
  118. jettyIdleTimeout = dim[1];
  119. break;
  120. }
  121. }
  122. }
  123. }
  124.  
  125. if (contextPath != null && !contextPath.startsWith("/")) {
  126. contextPath = "/" + contextPath;
  127. }
  128.  
  129. final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
  130.  
  131. final Server server = new Server();
  132.  
  133. final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS)
  134. .toLowerCase().split(",")).map(String::trim).collect(toSet());
  135.  
  136. final List<ServerConnector> connectorInstances = new ArrayList<>();
  137.  
  138. final HttpConfiguration httpConfig = new HttpConfiguration();
  139. httpConfig.setSendServerVersion(false);
  140. if (connectorsSet.contains(Connectors.HTTPS)) {
  141. httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
  142. }
  143. if (jettyIdleTimeout != null && jettyIdleTimeout.trim().length() != 0) {
  144. httpConfig.setIdleTimeout(Long.parseLong(jettyIdleTimeout.trim()));
  145. } else {
  146. httpConfig.setIdleTimeout(300000L); // default is 5min
  147. }
  148.  
  149. if (connectorsSet.contains(Connectors.HTTP)) {
  150. final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
  151. connector.setHost(hostName);
  152. connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt));
  153.  
  154. connectorInstances.add(connector);
  155. }
  156.  
  157. if (connectorsSet.contains(Connectors.HTTPS)) {
  158. final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
  159.  
  160. sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
  161. "You must specify a path to an SSL keystore via the --key_store_path command line argument" +
  162. " or GITBUCKET_KEYSTOREPATH environment variable."));
  163.  
  164. sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword,
  165. "You must specify a an SSL keystore password via the --key_store_password argument" +
  166. " or GITBUCKET_KEYSTOREPASSWORD environment variable."));
  167.  
  168. sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword,
  169. "You must specify a key manager password via the --key_manager_password' argument" +
  170. " or GITBUCKET_KEYMANAGERPASSWORD environment variable."));
  171.  
  172. final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
  173. httpsConfig.addCustomizer(new SecureRequestCustomizer());
  174.  
  175. final ServerConnector connector = new ServerConnector(server,
  176. new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
  177. new HttpConnectionFactory(httpsConfig));
  178.  
  179. connector.setHost(hostName);
  180. connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
  181.  
  182. connectorInstances.add(connector);
  183. }
  184.  
  185. require(!connectorInstances.isEmpty(),
  186. "No server connectors could be configured, please check your --connectors command line argument" +
  187. " or GITBUCKET_CONNECTORS environment variable.");
  188.  
  189. server.setConnectors(connectorInstances.toArray(new ServerConnector[0]));
  190.  
  191. WebAppContext context = new WebAppContext();
  192.  
  193. if(saveSessions) {
  194. File sessDir = new File(getGitBucketHome(), "sessions");
  195. if(!sessDir.exists()){
  196. mkdir(sessDir);
  197. }
  198. SessionHandler sessions = context.getSessionHandler();
  199. SessionCache cache = new DefaultSessionCache(sessions);
  200. FileSessionDataStore fsds = new FileSessionDataStore();
  201. fsds.setStoreDir(sessDir);
  202. cache.setSessionDataStore(fsds);
  203. sessions.setSessionCache(cache);
  204. }
  205.  
  206. File tmpDir;
  207. if(tmpDirPath == null || tmpDirPath.equals("")){
  208. tmpDir = new File(getGitBucketHome(), "tmp");
  209. if(!tmpDir.exists()){
  210. mkdir(tmpDir);
  211. }
  212. } else {
  213. tmpDir = new File(tmpDirPath);
  214. if(!tmpDir.exists()){
  215. throw new java.io.FileNotFoundException(
  216. String.format("temp_dir \"%s\" not found", tmpDirPath));
  217. } else if(!tmpDir.isDirectory()) {
  218. throw new IllegalArgumentException(
  219. String.format("temp_dir \"%s\" is not a directory", tmpDirPath));
  220. }
  221. }
  222. context.setTempDirectory(tmpDir);
  223.  
  224. // Disabling the directory listing feature.
  225. context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
  226.  
  227. ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
  228. URL location = domain.getCodeSource().getLocation();
  229.  
  230. context.setContextPath(contextPath == null ? "" : contextPath);
  231. context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
  232. context.setServer(server);
  233. context.setWar(location.toExternalForm());
  234.  
  235. final HandlerList handlers = new HandlerList();
  236.  
  237. if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) {
  238. handlers.addHandler(new SecuredRedirectHandler());
  239. }
  240.  
  241. handlers.addHandler(addStatisticsHandler(context));
  242.  
  243. server.setHandler(handlers);
  244. server.setStopAtShutdown(true);
  245. server.setStopTimeout(7_000);
  246. server.start();
  247. server.join();
  248. }
  249.  
  250. private static File getGitBucketHome(){
  251. String home = System.getProperty("gitbucket.home");
  252. if(home != null && home.length() > 0){
  253. return new File(home);
  254. }
  255. home = System.getenv("GITBUCKET_HOME");
  256. if(home != null && home.length() > 0){
  257. return new File(home);
  258. }
  259. return new File(System.getProperty("user.home"), ".gitbucket");
  260. }
  261.  
  262. private static String getEnvironmentVariable(String key){
  263. String value = System.getenv(key.toUpperCase().replace('.', '_'));
  264. if (value != null && value.length() == 0){
  265. return null;
  266. } else {
  267. return value;
  268. }
  269. }
  270.  
  271. private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
  272. return value == null ? defaultValue : converter.apply(value);
  273. }
  274.  
  275. private static <T> T fallback(T value, T defaultValue) {
  276. return fallback(value, defaultValue, identity());
  277. }
  278.  
  279. private static void require(boolean condition, String message) {
  280. if (!condition) {
  281. throw new IllegalArgumentException(message);
  282. }
  283. }
  284.  
  285. private static <T> T requireNonNull(T value, String message) {
  286. require(value != null, message);
  287. return value;
  288. }
  289.  
  290. private static void mkdir(File dir) {
  291. if (!dir.mkdirs()) {
  292. throw new RuntimeException("Unable to create directory: " + dir);
  293. }
  294. }
  295.  
  296. private static Handler addStatisticsHandler(Handler handler) {
  297. // The graceful shutdown is implemented via the statistics handler.
  298. // See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
  299. final StatisticsHandler statisticsHandler = new StatisticsHandler();
  300. statisticsHandler.setHandler(handler);
  301. return statisticsHandler;
  302. }
  303. }