import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.SecuredRedirectHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.session.DefaultSessionCache; import org.eclipse.jetty.server.session.FileSessionDataStore; import org.eclipse.jetty.server.session.SessionCache; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.webapp.WebAppContext; import java.io.File; import java.net.InetAddress; import java.net.URL; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toSet; public class JettyLauncher { private interface Defaults { String CONNECTORS = "http"; String HOST = "0.0.0.0"; int HTTP_PORT = 8080; int HTTPS_PORT = 8443; boolean REDIRECT_HTTPS = false; } private interface Connectors { String HTTP = "http"; String HTTPS = "https"; } public static void main(String[] args) throws Exception { System.setProperty("java.awt.headless", "true"); String connectors = getEnvironmentVariable("gitbucket.connectors"); String host = getEnvironmentVariable("gitbucket.host"); String port = getEnvironmentVariable("gitbucket.port"); String securePort = getEnvironmentVariable("gitbucket.securePort"); String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath"); String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword"); String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword"); String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps"); String contextPath = getEnvironmentVariable("gitbucket.prefix"); String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir"); boolean saveSessions = false; for(String arg: args) { if(arg.equals("--save_sessions")) { saveSessions = true; } if(arg.startsWith("--") && arg.contains("=")) { String[] dim = arg.split("=", 2); if(dim.length == 2) { switch (dim[0]) { case "--connectors": connectors = dim[1]; break; case "--host": host = dim[1]; break; case "--port": port = dim[1]; break; case "--secure_port": securePort = dim[1]; break; case "--key_store_path": keyStorePath = dim[1]; break; case "--key_store_password": keyStorePassword = dim[1]; break; case "--key_manager_password": keyManagerPassword = dim[1]; break; case "--redirect_https": redirectHttps = dim[1]; break; case "--prefix": contextPath = dim[1]; break; case "--gitbucket.home": System.setProperty("gitbucket.home", dim[1]); break; case "--temp_dir": tmpDirPath = dim[1]; break; case "--plugin_dir": System.setProperty("gitbucket.pluginDir", dim[1]); break; } } } } if (contextPath != null && !contextPath.startsWith("/")) { contextPath = "/" + contextPath; } final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName(); final Server server = new Server(); final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS) .toLowerCase().split(",")).map(String::trim).collect(toSet()); final List<ServerConnector> connectorInstances = new ArrayList<>(); final HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSendServerVersion(false); if (connectorsSet.contains(Connectors.HTTPS)) { httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt)); } if (connectorsSet.contains(Connectors.HTTP)) { final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); connector.setHost(hostName); connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt)); connectorInstances.add(connector); } if (connectorsSet.contains(Connectors.HTTPS)) { final SslContextFactory sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath, "You must specify a path to an SSL keystore via the --key_store_path command line argument" + " or GITBUCKET_KEYSTOREPATH environment variable.")); sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword, "You must specify a an SSL keystore password via the --key_store_password argument" + " or GITBUCKET_KEYSTOREPASSWORD environment variable.")); sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword, "You must specify a key manager password via the --key_manager_password' argument" + " or GITBUCKET_KEYMANAGERPASSWORD environment variable.")); final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); httpsConfig.addCustomizer(new SecureRequestCustomizer()); final ServerConnector connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConfig)); connector.setHost(hostName); connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt)); connectorInstances.add(connector); } require(!connectorInstances.isEmpty(), "No server connectors could be configured, please check your --connectors command line argument" + " or GITBUCKET_CONNECTORS environment variable."); server.setConnectors(connectorInstances.toArray(new ServerConnector[0])); WebAppContext context = new WebAppContext(); if(saveSessions) { File sessDir = new File(getGitBucketHome(), "sessions"); if(!sessDir.exists()){ mkdir(sessDir); } SessionHandler sessions = context.getSessionHandler(); SessionCache cache = new DefaultSessionCache(sessions); FileSessionDataStore fsds = new FileSessionDataStore(); fsds.setStoreDir(sessDir); cache.setSessionDataStore(fsds); sessions.setSessionCache(cache); } File tmpDir; if(tmpDirPath == null || tmpDirPath.equals("")){ tmpDir = new File(getGitBucketHome(), "tmp"); if(!tmpDir.exists()){ mkdir(tmpDir); } } else { tmpDir = new File(tmpDirPath); if(!tmpDir.exists()){ throw new java.io.FileNotFoundException( String.format("temp_dir \"%s\" not found", tmpDirPath)); } else if(!tmpDir.isDirectory()) { throw new IllegalArgumentException( String.format("temp_dir \"%s\" is not a directory", tmpDirPath)); } } context.setTempDirectory(tmpDir); // Disabling the directory listing feature. context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); ProtectionDomain domain = JettyLauncher.class.getProtectionDomain(); URL location = domain.getCodeSource().getLocation(); context.setContextPath(contextPath == null ? "" : contextPath); context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml"); context.setServer(server); context.setWar(location.toExternalForm()); final HandlerList handlers = new HandlerList(); if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) { handlers.addHandler(new SecuredRedirectHandler()); } handlers.addHandler(addStatisticsHandler(context)); server.setHandler(handlers); server.setStopAtShutdown(true); server.setStopTimeout(7_000); server.start(); server.join(); } private static File getGitBucketHome(){ String home = System.getProperty("gitbucket.home"); if(home != null && home.length() > 0){ return new File(home); } home = System.getenv("GITBUCKET_HOME"); if(home != null && home.length() > 0){ return new File(home); } return new File(System.getProperty("user.home"), ".gitbucket"); } private static String getEnvironmentVariable(String key){ String value = System.getenv(key.toUpperCase().replace('.', '_')); if (value != null && value.length() == 0){ return null; } else { return value; } } private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) { return value == null ? defaultValue : converter.apply(value); } private static <T> T fallback(T value, T defaultValue) { return fallback(value, defaultValue, identity()); } private static void require(boolean condition, String message) { if (!condition) { throw new IllegalArgumentException(message); } } private static <T> T requireNonNull(T value, String message) { require(value != null, message); return value; } private static void mkdir(File dir) { if (!dir.mkdirs()) { throw new RuntimeException("Unable to create directory: " + dir); } } private static Handler addStatisticsHandler(Handler handler) { // The graceful shutdown is implemented via the statistics handler. // See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142 final StatisticsHandler statisticsHandler = new StatisticsHandler(); statisticsHandler.setHandler(handler); return statisticsHandler; } }