From bbf1aaccde2cd0c9d9becff1f7a784466726abda Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 17 Dec 2009 12:16:45 -0800 Subject: [PATCH] init: Refactor init to be small parts created through Guice I was nuts when I wrote init. Making it all one giant program in a single class was simply insane. Instead we break it down into many smaller classes and use Guice to manage the creation, dependency injection, and control flow. Since init knew about most of the Files under the $site_path we put them all into a single immutable class called SitePaths and replace all references to these throughout the code base to use SitePaths and these well defined constants. init can now also import GerritServer.properties into the newer style gerrit.config and secure.config. This ensure the database settings are setup with the current defaults Change-Id: I4f5d8256497c1a97df35754dbe6193c78edde9e1 Signed-off-by: Shawn O. Pearce --- .../com/google/gerrit/httpd/GitWebConfig.java | 9 +- .../gerrit/httpd/gitweb/GitWebCssServlet.java | 27 +- .../gerrit/httpd/gitweb/GitWebServlet.java | 17 +- .../gerrit/httpd/raw/HostPageServlet.java | 24 +- .../gerrit/httpd/raw/StaticServlet.java | 6 +- .../main/java/com/google/gerrit/pgm/Init.java | 1247 ++--------------- .../gerrit/pgm/http/jetty/JettyServer.java | 18 +- .../com/google/gerrit/pgm/init/Browser.java | 92 ++ .../com/google/gerrit/pgm/init/InitAuth.java | 84 ++ .../com/google/gerrit/pgm/init/InitCache.java | 57 + .../google/gerrit/pgm/init/InitContainer.java | 95 ++ .../google/gerrit/pgm/init/InitDatabase.java | 105 ++ .../com/google/gerrit/pgm/init/InitFlags.java | 49 + .../gerrit/pgm/init/InitGitManager.java | 55 + .../com/google/gerrit/pgm/init/InitHttpd.java | 203 +++ .../google/gerrit/pgm/init/InitModule.java | 52 + .../google/gerrit/pgm/init/InitSendEmail.java | 58 + .../com/google/gerrit/pgm/init/InitSshd.java | 142 ++ .../com/google/gerrit/pgm/init/InitStep.java | 20 + .../com/google/gerrit/pgm/init/InitUtil.java | 214 +++ .../gerrit/pgm/{util => init}/Libraries.java | 26 +- .../pgm/{util => init}/LibraryDownloader.java | 56 +- .../gerrit/pgm/init/ReloadSiteLibrary.java | 20 + .../com/google/gerrit/pgm/init/Section.java | 167 +++ .../gerrit/pgm/init/SitePathInitializer.java | 101 ++ .../gerrit/pgm/init/UpgradeFrom2_0_x.java | 290 ++++ .../com/google/gerrit/pgm/util/ConsoleUI.java | 12 + .../gerrit/pgm/util/DataSourceProvider.java | 21 +- .../google/gerrit/pgm/util/SiteProgram.java | 8 +- .../google/gerrit/server/cache/CachePool.java | 23 +- .../config/GerritServerConfigModule.java | 1 + .../config/GerritServerConfigProvider.java | 20 +- .../gerrit/server/config/SitePaths.java | 113 ++ .../server/contact/ContactStoreProvider.java | 10 +- .../gerrit/server/git/GitProjectImporter.java | 109 ++ .../server/git/GitRepositoryManager.java | 40 +- .../gerrit/server/git/PushReplication.java | 21 +- .../gerrit/server/schema/SchemaCreator.java | 50 +- .../gerrit/server/schema/SchemaUpdater.java | 14 +- .../google/gerrit/server/util/SocketUtil.java | 123 ++ .../google/gerrit/sshd/HostKeyProvider.java | 28 +- .../com/google/gerrit/sshd/SshDaemon.java | 83 +- 42 files changed, 2506 insertions(+), 1404 deletions(-) create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java rename gerrit-pgm/src/main/java/com/google/gerrit/pgm/{util => init}/Libraries.java (86%) rename gerrit-pgm/src/main/java/com/google/gerrit/pgm/{util => init}/LibraryDownloader.java (83%) create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ReloadSiteLibrary.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java create mode 100644 gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/util/SocketUtil.java diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java index feeaa41bf6..eb21728ebb 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java @@ -15,7 +15,7 @@ package com.google.gerrit.httpd; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import org.eclipse.jgit.lib.Config; @@ -33,7 +33,7 @@ public class GitWebConfig { private final File git_logo_png; @Inject - GitWebConfig(@SitePath final File sitePath, + GitWebConfig(final SitePaths sitePaths, @GerritServerConfig final Config cfg) { final String cfgUrl = cfg.getString("gitweb", null, "url"); final String cfgCgi = cfg.getString("gitweb", null, "cgi"); @@ -65,10 +65,7 @@ public class GitWebConfig { // Use the CGI script configured by the administrator, failing if it // cannot be used as specified. // - cgi = new File(cfgCgi); - if (!cgi.isAbsolute()) { - cgi = new File(sitePath, cfgCgi).getAbsoluteFile(); - } + cgi = sitePaths.resolve(cfgCgi); if (!cgi.isFile()) { throw new IllegalStateException("Cannot find gitweb.cgi: " + cgi); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java index 243616f3ef..1e6d35e187 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebCssServlet.java @@ -31,7 +31,7 @@ package com.google.gerrit.httpd.gitweb; import com.google.gerrit.httpd.GitWebConfig; import com.google.gerrit.httpd.HtmlDomUtil; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -50,13 +50,8 @@ abstract class GitWebCssServlet extends HttpServlet { @Singleton static class Site extends GitWebCssServlet { @Inject - Site(@SitePath File sp, GitWebConfig gwc) throws IOException { - super(sp, gwc); - } - - @Override - protected File path(File etc, GitWebConfig gitWebConfig) { - return new File(etc, "GerritSite.css"); + Site(SitePaths paths, GitWebConfig gwc) throws IOException { + super(paths.site_css, gwc); } } @@ -64,13 +59,8 @@ abstract class GitWebCssServlet extends HttpServlet { @Singleton static class Default extends GitWebCssServlet { @Inject - Default(@SitePath File sp, GitWebConfig gwc) throws IOException { - super(sp, gwc); - } - - @Override - protected File path(File etc, GitWebConfig gitWebConfig) { - return gitWebConfig.getGitwebCSS(); + Default(GitWebConfig gwc) throws IOException { + super(gwc.getGitwebCSS(), gwc); } } @@ -78,9 +68,8 @@ abstract class GitWebCssServlet extends HttpServlet { private final byte[] raw_css; private final byte[] gz_css; - GitWebCssServlet(@SitePath final File sitePath, - final GitWebConfig gitWebConfig) throws IOException { - final File src = path(new File(sitePath, "etc"), gitWebConfig); + GitWebCssServlet(final File src, final GitWebConfig gitWebConfig) + throws IOException { final File dir = src.getParentFile(); final String name = src.getName(); final String raw = HtmlDomUtil.readFile(dir, name); @@ -93,8 +82,6 @@ abstract class GitWebCssServlet extends HttpServlet { } } - protected abstract File path(File sitePath, GitWebConfig gitWebConfig); - @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse rsp) throws IOException { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java index 7ff25d2196..1f8570f3e5 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java @@ -33,7 +33,7 @@ import com.google.gerrit.common.data.GerritConfig; import com.google.gerrit.httpd.GitWebConfig; import com.google.gerrit.reviewdb.Project; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.ProjectControl; @@ -84,7 +84,7 @@ class GitWebServlet extends HttpServlet { @Inject GitWebServlet(final GitRepositoryManager repoManager, final ProjectControl.Factory projectControl, - @SitePath final File sitePath, final GerritConfig gerritConfig, + final SitePaths site, final GerritConfig gerritConfig, final GitWebConfig gitWebConfig) throws IOException { this.repoManager = repoManager; this.projectControl = projectControl; @@ -97,7 +97,7 @@ class GitWebServlet extends HttpServlet { deniedActions.add("project_index"); _env = new EnvList(); - makeSiteConfig(sitePath, gerritConfig); + makeSiteConfig(site, gerritConfig); if (!_env.envMap.containsKey("SystemRoot")) { String os = System.getProperty("os.name"); @@ -115,7 +115,7 @@ class GitWebServlet extends HttpServlet { } } - private void makeSiteConfig(final File sitePath, + private void makeSiteConfig(final SitePaths site, final GerritConfig gerritConfig) throws IOException { final File myconf = File.createTempFile("gitweb_config_", ".perl"); @@ -133,7 +133,6 @@ class GitWebServlet extends HttpServlet { _env.set("GIT_DIR", "."); _env.set("GITWEB_CONFIG", myconf.getAbsolutePath()); - final File etc = new File(sitePath, "etc"); final PrintWriter p = new PrintWriter(new FileWriter(myconf)); try { p.print("# Autogenerated by Gerrit Code Review \n"); @@ -143,11 +142,11 @@ class GitWebServlet extends HttpServlet { // We are mounted at the same level in the context as the main // UI, so we can include the same header and footer scheme. // - final File hdr = new File(etc, "GerritSiteHeader.html"); + final File hdr = site.site_header; if (hdr.isFile()) { p.print("$site_header = " + quoteForPerl(hdr) + ";\n"); } - final File ftr = new File(etc, "GerritSiteFooter.html"); + final File ftr = site.site_footer; if (ftr.isFile()) { p.print("$site_footer = " + quoteForPerl(ftr) + ";\n"); } @@ -160,7 +159,7 @@ class GitWebServlet extends HttpServlet { p.print("$favicon = 'favicon.ico';\n"); p.print("$logo = 'gitweb-logo.png';\n"); p.print("@stylesheets = ('gitweb-default.css');\n"); - final File css = new File(etc, "GerritSite.css"); + final File css = site.site_css; if (css.isFile()) { p.print("push @stylesheets, 'gitweb-site.css';\n"); } @@ -210,7 +209,7 @@ class GitWebServlet extends HttpServlet { // If the administrator has created a site-specific gitweb_config, // load that before we perform any final overrides. // - final File sitecfg = new File(etc, "gitweb_config.perl"); + final File sitecfg = site.site_gitweb; if (sitecfg.isFile()) { p.print("$GITWEB_CONFIG = " + quoteForPerl(sitecfg) + ";\n"); p.print("if (-e $GITWEB_CONFIG) {\n"); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java index 3496595420..14277a3f95 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java @@ -20,7 +20,7 @@ import com.google.gerrit.httpd.HtmlDomUtil; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.gwtjsonrpc.server.JsonServlet; import com.google.inject.Inject; @@ -55,10 +55,9 @@ public class HostPageServlet extends HttpServlet { private final Document hostDoc; @Inject - HostPageServlet(final Provider cu, - @SitePath final File sitePath, final GerritConfig gc, - @GerritServerConfig final Config cfg, final ServletContext servletContext) - throws IOException { + HostPageServlet(final Provider cu, final SitePaths site, + final GerritConfig gc, @GerritServerConfig final Config cfg, + final ServletContext servletContext) throws IOException { currentUser = cu; config = gc; @@ -68,15 +67,14 @@ public class HostPageServlet extends HttpServlet { throw new FileNotFoundException("No " + pageName + " in webapp"); } - final File etc = new File(sitePath, "etc"); fixModuleReference(hostDoc, servletContext); - injectCssFile(hostDoc, "gerrit_sitecss", etc, "GerritSite.css"); - injectXmlFile(hostDoc, "gerrit_header", etc, "GerritSiteHeader.html"); - injectXmlFile(hostDoc, "gerrit_footer", etc, "GerritSiteFooter.html"); + injectCssFile(hostDoc, "gerrit_sitecss", site.site_css); + injectXmlFile(hostDoc, "gerrit_header", site.site_header); + injectXmlFile(hostDoc, "gerrit_footer", site.site_footer); } private void injectXmlFile(final Document hostDoc, final String id, - final File etc, final String fileName) throws IOException { + final File src) throws IOException { final Element banner = HtmlDomUtil.find(hostDoc, id); if (banner == null) { return; @@ -86,7 +84,7 @@ public class HostPageServlet extends HttpServlet { banner.removeChild(banner.getFirstChild()); } - final Document html = HtmlDomUtil.parseFile(etc, fileName); + Document html = HtmlDomUtil.parseFile(src.getParentFile(), src.getName()); if (html == null) { banner.getParentNode().removeChild(banner); return; @@ -97,7 +95,7 @@ public class HostPageServlet extends HttpServlet { } private void injectCssFile(final Document hostDoc, final String id, - final File etc, final String fileName) throws IOException { + final File src) throws IOException { final Element banner = HtmlDomUtil.find(hostDoc, id); if (banner == null) { return; @@ -107,7 +105,7 @@ public class HostPageServlet extends HttpServlet { banner.removeChild(banner.getFirstChild()); } - final String css = HtmlDomUtil.readFile(etc, fileName); + final String css = HtmlDomUtil.readFile(src.getParentFile(), src.getName()); if (css == null) { banner.getParentNode().removeChild(banner); return; diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java index 8072feae51..ea587a4c17 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java @@ -14,7 +14,7 @@ package com.google.gerrit.httpd.raw; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -91,8 +91,8 @@ public class StaticServlet extends HttpServlet { private final File staticBase; @Inject - StaticServlet(@SitePath final File sitePath) { - staticBase = new File(sitePath, "static"); + StaticServlet(final SitePaths site) { + staticBase = site.static_dir; } private File local(final HttpServletRequest req) { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java index 64a4f68438..110a03be63 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java @@ -15,61 +15,39 @@ package com.google.gerrit.pgm; import static com.google.gerrit.pgm.util.DataSourceProvider.Context.SINGLE_USER; -import static com.google.gerrit.pgm.util.DataSourceProvider.Type.H2; +import static com.google.inject.Stage.PRODUCTION; import com.google.gerrit.common.PageLinks; -import com.google.gerrit.main.GerritLauncher; +import com.google.gerrit.pgm.init.Browser; +import com.google.gerrit.pgm.init.InitFlags; +import com.google.gerrit.pgm.init.InitModule; +import com.google.gerrit.pgm.init.ReloadSiteLibrary; +import com.google.gerrit.pgm.init.SitePathInitializer; import com.google.gerrit.pgm.util.ConsoleUI; -import com.google.gerrit.pgm.util.DataSourceProvider; +import com.google.gerrit.pgm.util.Die; import com.google.gerrit.pgm.util.ErrorLogFile; import com.google.gerrit.pgm.util.IoUtil; -import com.google.gerrit.pgm.util.Libraries; import com.google.gerrit.pgm.util.SiteProgram; -import com.google.gerrit.reviewdb.AuthType; -import com.google.gerrit.reviewdb.Project; -import com.google.gerrit.reviewdb.ReviewDb; -import com.google.gerrit.reviewdb.Project.SubmitType; -import com.google.gerrit.server.config.ConfigUtil; +import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.git.GitProjectImporter; import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.mail.SmtpEmailSender.Encryption; import com.google.gerrit.server.schema.SchemaUpdater; -import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtorm.client.OrmException; -import com.google.gwtorm.client.SchemaFactory; import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Module; +import com.google.inject.spi.Message; -import org.apache.sshd.common.util.SecurityUtils; -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.LockFile; -import org.eclipse.jgit.lib.RepositoryCache.FileKey; -import org.eclipse.jgit.util.SystemReader; -import org.h2.util.StartBrowser; import org.kohsuke.args4j.Option; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** Initialize a new Gerrit installation. */ public class Init extends SiteProgram { @@ -82,953 +60,198 @@ public class Init extends SiteProgram { @Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init") private boolean noAutoStart; - @Inject - private SchemaUpdater schemaUpdater; - - @Inject - private GitRepositoryManager repositoryManager; - - @Inject - private SchemaFactory schema; - - private boolean isNew; - private boolean deleteOnFailure; - private ConsoleUI ui; - private Libraries libraries; - private Injector dbInjector; - private Injector sysInjector; - - private File site_path; - private File bin_dir; - private File etc_dir; - private File lib_dir; - private File logs_dir; - private File static_dir; - - private File gerrit_sh; - private File gerrit_config; - private File secure_config; - private File replication_config; - - private FileBasedConfig cfg; - private FileBasedConfig sec; - @Override public int run() throws Exception { ErrorLogFile.errorOnlyConsole(); - ui = ConsoleUI.getInstance(batchMode); - libraries = new Libraries(ui, getSitePath()); - initPathLocations(); - if (site_path.exists()) { - final String[] contents = site_path.list(); - if (contents != null) - isNew = contents.length == 0; - else if (site_path.isDirectory()) - throw die("Cannot access " + site_path); - else - throw die("Not a directory: " + site_path); - } else { - isNew = true; - } + final SiteInit init = createSiteInit(); + init.flags.importProjects = importProjects; + init.flags.autoStart = !noAutoStart && init.site.isNew; + final SiteRun run; try { - upgradeFrom2_0(); + init.initializer.run(); + init.flags.deleteOnFailure = false; - initSitePath(); - generateSshHostKeys(); - deleteOnFailure = false; - - inject(); - schemaUpdater.update(); - initGit(); + run = createSiteRun(init); + run.schemaUpdater.update(); + run.importGit(); } catch (Exception failure) { - if (deleteOnFailure) { + if (init.flags.deleteOnFailure) { recursiveDelete(getSitePath()); } throw failure; } catch (Error failure) { - if (deleteOnFailure) { + if (init.flags.deleteOnFailure) { recursiveDelete(getSitePath()); } throw failure; } System.err.println("Initialized " + getSitePath().getCanonicalPath()); - - if (isNew && !noAutoStart) { - if (IoUtil.isWin32()) { - System.err.println("Automatic startup not supported on this platform."); - - } else { - start(); - openBrowser(); - } - } - + run.start(); return 0; } - private void initPathLocations() { - site_path = getSitePath(); + static class SiteInit { + final SitePaths site; + final InitFlags flags; + final ConsoleUI ui; + final SitePathInitializer initializer; - bin_dir = new File(site_path, "bin"); - etc_dir = new File(site_path, "etc"); - lib_dir = new File(site_path, "lib"); - logs_dir = new File(site_path, "logs"); - static_dir = new File(site_path, "static"); - - gerrit_sh = new File(bin_dir, "gerrit.sh"); - gerrit_config = new File(etc_dir, "gerrit.config"); - secure_config = new File(etc_dir, "secure.config"); - replication_config = new File(etc_dir, "replication.config"); - - cfg = new FileBasedConfig(gerrit_config); - sec = new FileBasedConfig(secure_config); + @Inject + SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui, + final SitePathInitializer initializer) { + this.site = site; + this.flags = flags; + this.ui = ui; + this.initializer = initializer; + } } - private void upgradeFrom2_0() throws IOException { - boolean isPre2_1 = false; - final String[] etcFiles = - {"gerrit.config", "secure.config", "replication.config", - "ssh_host_rsa_key", "ssh_host_rsa_key.pub", "ssh_host_dsa_key", - "ssh_host_dsa_key.pub", "ssh_host_key", "contact_information.pub", - "gitweb_config.perl", "keystore", "GerritSite.css", - "GerritSiteFooter.html", "GerritSiteHeader.html"}; - for (String name : etcFiles) { - if (new File(site_path, name).exists()) { - isPre2_1 = true; - break; - } - } + private SiteInit createSiteInit() { + final ConsoleUI ui = ConsoleUI.getInstance(batchMode); + final File sitePath = getSitePath(); + final List m = new ArrayList(); - if (isPre2_1) { - if (!ui.yesno(true, "Upgrade '%s'", site_path.getCanonicalPath())) { - throw die("aborted by user"); - } - - mkdir(etc_dir); - for (String name : etcFiles) { - final File src = new File(site_path, name); - final File dst = new File(etc_dir, name); - if (src.exists()) { - if (dst.exists()) { - throw die("File " + src + " would overwrite " + dst); + m.add(new InitModule()); + m.add(new AbstractModule() { + @Override + protected void configure() { + bind(ConsoleUI.class).toInstance(ui); + bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); + bind(ReloadSiteLibrary.class).toInstance(new ReloadSiteLibrary() { + @Override + public void reload() { + Init.super.loadSiteLib(); } - if (!src.renameTo(dst)) { - throw die("Cannot rename " + src + " to " + dst); + }); + } + }); + + try { + return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class); + } catch (CreationException ce) { + final Message first = ce.getErrorMessages().iterator().next(); + Throwable why = first.getCause(); + + if (why instanceof Die) { + throw (Die) why; + } + + final StringBuilder buf = new StringBuilder(); + buf.append(why.getMessage()); + why = why.getCause(); + while (why != null) { + buf.append("\n caused by "); + buf.append(why.toString()); + why = why.getCause(); + } + throw die(buf.toString(), new RuntimeException("InitInjector failed", ce)); + } + } + + static class SiteRun { + final ConsoleUI ui; + final SitePaths site; + final InitFlags flags; + final SchemaUpdater schemaUpdater; + final GitRepositoryManager repositoryManager; + final GitProjectImporter gitProjectImporter; + final Browser browser; + + @Inject + SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags, + final SchemaUpdater schemaUpdater, + final GitRepositoryManager repositoryManager, + final GitProjectImporter gitProjectImporter, final Browser browser) { + this.ui = ui; + this.site = site; + this.flags = flags; + this.schemaUpdater = schemaUpdater; + this.repositoryManager = repositoryManager; + this.gitProjectImporter = gitProjectImporter; + this.browser = browser; + } + + void importGit() throws OrmException, IOException { + if (flags.importProjects) { + System.err.println("Scanning " + repositoryManager.getBasePath()); + gitProjectImporter.run(new GitProjectImporter.Messages() { + @Override + public void warning(String msg) { + System.err.println(msg); + System.err.flush(); } - } + }); } } - } - private void initSitePath() throws IOException, InterruptedException, - ConfigInvalidException { - ui.header("Gerrit Code Review %s", version()); + void start() throws IOException { + if (flags.autoStart) { + if (IoUtil.isWin32()) { + System.err.println("Automatic startup not supported on Win32."); - if (isNew) { - if (!ui.yesno(true, "Create '%s'", site_path.getCanonicalPath())) { - throw die("aborted by user"); - } - if (!site_path.isDirectory() && !site_path.mkdirs()) { - throw die("Cannot make directory " + site_path); - } - deleteOnFailure = true; - } - - mkdir(bin_dir); - mkdir(etc_dir); - mkdir(lib_dir); - mkdir(logs_dir); - mkdir(static_dir); - - cfg.load(); - sec.load(); - - downloadOptionalLibraries(); - init_gerrit_basepath(); - init_database(); - init_auth(); - init_sendemail(); - init_container(); - init_sshd(); - init_httpd(); - init_cache(); - - cfg.save(); - saveSecureConfig(sec); - - if (ui != null) { - System.err.println(); - } - - if (!replication_config.exists()) { - replication_config.createNewFile(); - } - - extract(gerrit_sh, Init.class, "gerrit.sh"); - chmod(0755, gerrit_sh); - } - - private void initGit() throws OrmException, IOException { - final File root = repositoryManager.getBasePath(); - if (root != null && importProjects) { - System.err.println("Scanning projects under " + root); - final ReviewDb db = schema.open(); - try { - final HashSet have = new HashSet(); - for (Project p : db.projects().all()) { - have.add(p.getName()); - } - importProjects(root, "", db, have); - } finally { - db.close(); - } - } - } - - private void importProjects(final File dir, final String prefix, - final ReviewDb db, final Set have) throws OrmException, - IOException { - final File[] ls = dir.listFiles(); - if (ls == null) { - return; - } - - for (File f : ls) { - if (".".equals(f.getName()) || "..".equals(f.getName())) { - } else if (FileKey.isGitRepository(f)) { - String name = f.getName(); - if (name.equals(".git")) { - name = prefix.substring(0, prefix.length() - 1); - } else if (name.endsWith(".git")) { - name = prefix + name.substring(0, name.length() - 4); } else { - name = prefix + name; - System.err.println("Ignoring non-standard name '" + name + "'"); - continue; - } - - if (have.contains(name)) { - continue; - } - - final Project.NameKey nameKey = new Project.NameKey(name); - final Project.Id idKey = new Project.Id(db.nextProjectId()); - final Project p = new Project(nameKey, idKey); - - p.setDescription(repositoryManager.getProjectDescription(name)); - p.setSubmitType(SubmitType.MERGE_IF_NECESSARY); - p.setUseContributorAgreements(false); - p.setUseSignedOffBy(false); - db.projects().insert(Collections.singleton(p)); - - } else if (f.isDirectory()) { - importProjects(f, prefix + f.getName() + "/", db, have); - } - } - } - - private void saveSecureConfig(final FileBasedConfig sec) throws IOException { - final byte[] out = Constants.encode(sec.toText()); - final File path = sec.getFile(); - final LockFile lf = new LockFile(path); - if (!lf.lock()) { - throw new IOException("Cannot lock " + path); - } - try { - chmod(0600, new File(path.getParentFile(), path.getName() + ".lock")); - lf.write(out); - if (!lf.commit()) { - throw new IOException("Cannot commit write to " + path); - } - } finally { - lf.unlock(); - } - } - - private static void mkdir(final File path) { - if (!path.isDirectory() && !path.mkdir()) { - throw die("Cannot make directory " + path); - } - } - - private static void chmod(final int mode, final File path) { - path.setReadable(false, false /* all */); - path.setWritable(false, false /* all */); - path.setExecutable(false, false /* all */); - - path.setReadable((mode & 0400) == 0400, true /* owner only */); - path.setWritable((mode & 0200) == 0200, true /* owner only */); - if (path.isDirectory() || (mode & 0100) == 0100) { - path.setExecutable(true, true /* owner only */); - } - - if ((mode & 0044) == 0044) { - path.setReadable(true, false /* all */); - } - if ((mode & 0011) == 0011) { - path.setExecutable(true, false /* all */); - } - } - - private void downloadOptionalLibraries() { - // Download and install BouncyCastle if the user wants to use it. - // - libraries.bouncyCastle.downloadOptional(); - loadSiteLib(); - } - - private void init_gerrit_basepath() { - final Section cfg = new Section("gerrit"); - ui.header("Git Repositories"); - - File d = cfg.path("Location of Git repositories", "basePath", "git"); - if (d == null) { - throw die("gerrit.basePath is required"); - } - if (d.exists()) { - if (!importProjects && d.list() != null && d.list().length > 0) { - importProjects = ui.yesno(true, "Import existing repositories"); - } - } else if (!d.mkdirs()) { - throw die("Cannot create " + d); - } - } - - private void init_database() { - final Section cfg = new Section("database"); - ui.header("SQL Database"); - - final DataSourceProvider.Type db_type = - cfg.select("Database server type", "type", H2); - - switch (db_type) { - case MYSQL: - libraries.mysqlDriver.downloadRequired(); - loadSiteLib(); - break; - } - - final boolean userPassAuth; - switch (db_type) { - case H2: { - userPassAuth = false; - String path = cfg.get("database"); - if (path == null) { - path = "db/ReviewDB"; - cfg.set("database", path); - } - File db = resolve(path); - if (db == null) { - throw die("database.database must be supplied for H2"); - } - db = db.getParentFile(); - if (!db.exists() && !db.mkdirs()) { - throw die("cannot create database.database " + db.getAbsolutePath()); - } - break; - } - - case JDBC: { - userPassAuth = true; - cfg.string("Driver class name", "driver", null); - cfg.string("URL", "url", null); - break; - } - - case POSTGRES: - case POSTGRESQL: - case MYSQL: { - userPassAuth = true; - final String defPort = "(" + db_type.toString() + " default)"; - cfg.string("Server hostname", "hostname", "localhost"); - cfg.string("Server port", "port", defPort, true); - cfg.string("Database name", "database", "reviewdb"); - break; - } - - default: - throw die("internal bug, database " + db_type + " not supported"); - } - - if (userPassAuth) { - cfg.string("Database username", "username", username()); - cfg.password("username", "password"); - } - } - - private void init_auth() { - final Section auth = new Section("auth"); - final Section ldap = new Section("ldap"); - ui.header("User Authentication"); - - final AuthType auth_type = - auth.select("Authentication method", "type", AuthType.OPENID); - - switch (auth_type) { - case HTTP: - case HTTP_LDAP: { - String hdr = auth.get("httpHeader"); - if (ui.yesno(hdr != null, "Get username from custom HTTP header")) { - auth.string("Username HTTP header", "httpHeader", "SM_USER"); - } else if (hdr != null) { - auth.unset("httpHeader"); - } - auth.string("SSO logout URL", "logoutUrl", null); - break; - } - } - - switch (auth_type) { - case LDAP: - case HTTP_LDAP: { - String server = - ldap.string("LDAP server", "server", "ldap://localhost"); - if (server != null // - && !server.startsWith("ldap://") // - && !server.startsWith("ldaps://")) { - if (ui.yesno(false, "Use SSL")) { - server = "ldaps://" + server; - } else { - server = "ldap://" + server; - } - ldap.set("server", server); - } - - ldap.string("LDAP username", "username", null); - ldap.password("username", "password"); - - final String def_dn = dnOf(server); - String aBase = ldap.string("Account BaseDN", "accountBase", def_dn); - String gBase = ldap.string("Group BaseDN", "groupBase", aBase); - break; - } - } - } - - private void init_sendemail() { - final Section cfg = new Section("sendemail"); - ui.header("Email Delivery"); - - final String hostname = - cfg.string("SMTP server hostname", "smtpServer", "localhost"); - cfg.string("SMTP server port", "smtpServerPort", "(default)", true); - final Encryption enc = - cfg.select("SMTP encryption", "smtpEncryption", Encryption.NONE, true); - - String username = null; - if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) { - username = username(); - } - cfg.string("SMTP username", "smtpUser", username); - cfg.password("smtpUser", "smtpPass"); - } - - private void init_container() throws IOException { - final Section cfg = new Section("container"); - ui.header("Container Process"); - - cfg.string("Run as", "user", username()); - cfg.string("Java runtime", "javaHome", System.getProperty("java.home")); - - final File siteWar = new File(bin_dir, "gerrit.war"); - File myWar; - try { - myWar = GerritLauncher.getDistributionArchive(); - } catch (FileNotFoundException e) { - System.err.println("warn: Cannot find gerrit.war"); - myWar = null; - } - - String path = cfg.get("war"); - if (path != null) { - path = cfg.string("Gerrit runtime", "war", // - myWar != null ? myWar.getAbsolutePath() : null); - if (path == null || path.isEmpty()) { - throw die("container.war is required"); - } - - } else if (myWar != null) { - final boolean copy; - if (siteWar.exists()) { - copy = ui.yesno(true, "Upgrade %s", siteWar.getPath()); - } else { - copy = ui.yesno(true, "Copy gerrit.war to %s", siteWar.getPath()); - if (copy) { - cfg.unset("war"); - } else { - cfg.set("war", myWar.getAbsolutePath()); - } - } - if (copy) { - if (!ui.isBatch()) { - System.err.format("Copying gerrit.war to %s", siteWar.getPath()); - System.err.println(); - } - copy(siteWar, new FileInputStream(myWar)); - } - } - } - - private void init_sshd() { - final Section cfg = new Section("sshd"); - ui.header("SSH Daemon"); - - String hostname = "*", port = "29418"; - String listenAddress = cfg.get("listenAddress"); - if (listenAddress != null && !listenAddress.isEmpty()) { - if (listenAddress.startsWith("[")) { - final int hostEnd = listenAddress.indexOf(']'); - if (0 < hostEnd) { - hostname = listenAddress.substring(1, hostEnd); - if (hostEnd + 1 < listenAddress.length() // - && listenAddress.charAt(hostEnd + 1) == ':') { - port = listenAddress.substring(hostEnd + 2); + startDaemon(); + if (!ui.isBatch()) { + browser.open(PageLinks.ADMIN_PROJECTS); } } - - } else { - final int hostEnd = listenAddress.indexOf(':'); - if (0 < hostEnd) { - hostname = listenAddress.substring(0, hostEnd); - port = listenAddress.substring(hostEnd + 1); - } else { - hostname = listenAddress; - } } } - hostname = ui.readString(hostname, "Listen on address"); - port = ui.readString(port, "Listen on port"); - cfg.set("listenAddress", hostname + ":" + port); - } - - private void init_httpd() throws IOException, InterruptedException { - final Section httpd = new Section("httpd"); - ui.header("HTTP Daemon"); - - boolean proxy = false, ssl = false; - String address = "*", port = null, context = "/"; - String listenUrl = httpd.get("listenUrl"); - if (listenUrl != null && !listenUrl.isEmpty()) { + void startDaemon() { + final String[] argv = {site.gerrit_sh.getAbsolutePath(), "start"}; + final Process proc; try { - final URI uri = toURI(listenUrl); - proxy = uri.getScheme().startsWith("proxy-"); - ssl = uri.getScheme().endsWith("https"); - address = isAnyAddress(new URI(listenUrl)) ? "*" : uri.getHost(); - port = String.valueOf(uri.getPort()); - context = uri.getPath(); - } catch (URISyntaxException e) { - System.err.println("warning: invalid httpd.listenUrl " + listenUrl); - } - } - - proxy = ui.yesno(proxy, "Behind reverse proxy"); - - if (proxy) { - ssl = ui.yesno(ssl, "Proxy uses SSL (https://)"); - context = ui.readString(context, "Subdirectory on proxy server"); - } else { - ssl = ui.yesno(ssl, "Use SSL (https://)"); - context = "/"; - } - - address = ui.readString(address, "Listen on address"); - - if (port == null) { - if (proxy) { - port = "8081"; - } else if (ssl) { - port = "8443"; - } else { - port = "8080"; - } - } - port = ui.readString(port, "Listen on port"); - - final StringBuilder urlbuf = new StringBuilder(); - urlbuf.append(proxy ? "proxy-" : ""); - urlbuf.append(ssl ? "https" : "http"); - urlbuf.append("://"); - urlbuf.append(address); - urlbuf.append(":"); - urlbuf.append(port); - urlbuf.append(context); - httpd.set("listenUrl", urlbuf.toString()); - - URI uri; - try { - uri = toURI(httpd.get("listenUrl")); - if (uri.getScheme().startsWith("proxy-")) { - // If its a proxy URL, assume the reverse proxy is on our system - // at the protocol standard ports (so omit the ports from the URL). - // - String s = uri.getScheme().substring("proxy-".length()); - uri = new URI(s + "://" + uri.getHost() + uri.getPath()); - } - } catch (URISyntaxException e) { - throw die("invalid httpd.listenUrl"); - } - final Section gerrit = new Section("gerrit"); - if (gerrit.get("canonicalWebUrl") != null - || (!proxy && ssl) - || ConfigUtil.getEnum(cfg, "auth", null, "type", AuthType.OPENID) == AuthType.OPENID) { - gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString()); - } - - generateSslCertificate(); - } - - private void generateSslCertificate() throws IOException, - InterruptedException { - final Section httpd = new Section("httpd"); - final String listenUrl = httpd.get("listenUrl"); - - if (!listenUrl.startsWith("https://")) { - // We aren't responsible for SSL processing. - // - return; - } - - String hostname; - try { - String url = cfg.getString("gerrit", null, "canonicalWebUrl"); - if (url == null || url.isEmpty()) { - url = listenUrl; - } - hostname = toURI(url).getHost(); - } catch (URISyntaxException e) { - System.err.println("Invalid httpd.listenUrl, not checking certificate"); - return; - } - - final File store = new File(etc_dir, "keystore"); - if (!ui.yesno(!store.exists(), "Create new self-signed SSL certificate")) { - return; - } - - String ssl_pass = sec.getString("http", null, "sslKeyPassword"); - if (ssl_pass == null || ssl_pass.isEmpty()) { - ssl_pass = SignedToken.generateRandomKey(); - sec.setString("httpd", null, "sslKeyPassword", ssl_pass); - } - - hostname = ui.readString(hostname, "Certificate server name"); - final String validity = - ui.readString("365", "Certificate expires in (days)"); - - final String dname = - "CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname); - - final File tmpdir = new File(etc_dir, "tmp.sslcertgen"); - if (!tmpdir.mkdir()) { - throw die("Cannot create directory " + tmpdir); - } - chmod(0600, tmpdir); - - final File tmpstore = new File(tmpdir, "keystore"); - Runtime.getRuntime().exec(new String[] {"keytool", // - "-keystore", tmpstore.getAbsolutePath(), // - "-storepass", ssl_pass, // - "-genkeypair", // - "-alias", hostname, // - "-keyalg", "RSA", // - "-validity", validity, // - "-dname", dname, // - "-keypass", ssl_pass, // - }).waitFor(); - chmod(0600, tmpstore); - - if (!tmpstore.renameTo(store)) { - throw die("Cannot rename " + tmpstore + " to " + store); - } - if (!tmpdir.delete()) { - throw die("Cannot delete " + tmpdir); - } - } - - private void init_cache() { - final Section cfg = new Section("cache"); - String path = cfg.get("directory"); - - if (path != null && path.isEmpty()) { - // Explicitly set to empty implies the administrator has - // disabled the on disk cache and doesn't want it enabled. - // - return; - } - - if (path == null) { - path = "cache"; - cfg.set("directory", path); - } - - final File loc = resolve(path); - if (!loc.exists() && !loc.mkdirs()) { - throw die("cannot create cache.directory " + loc.getAbsolutePath()); - } - } - - private File resolve(final String path) { - if (path != null && !path.isEmpty()) { - File loc = new File(path); - if (!loc.isAbsolute()) { - loc = new File(site_path, path); - } - return loc; - } - return null; - } - - private void generateSshHostKeys() throws InterruptedException, IOException { - final File key = new File(etc_dir, "ssh_host_key"); - final File rsa = new File(etc_dir, "ssh_host_rsa_key"); - final File dsa = new File(etc_dir, "ssh_host_dsa_key"); - - if (!key.exists() && !rsa.exists() && !dsa.exists()) { - System.err.print("Generating SSH host key ..."); - System.err.flush(); - - if (SecurityUtils.isBouncyCastleRegistered()) { - // Generate the SSH daemon host key using ssh-keygen. - // - final String comment = "gerrit-code-review@" + hostname(); - - System.err.print(" rsa..."); - System.err.flush(); - Runtime.getRuntime().exec(new String[] {"ssh-keygen", // - "-q" /* quiet */, // - "-t", "rsa", // - "-P", "", // - "-C", comment, // - "-f", rsa.getAbsolutePath() // - }).waitFor(); - - System.err.print(" dsa..."); - System.err.flush(); - Runtime.getRuntime().exec(new String[] {"ssh-keygen", // - "-q" /* quiet */, // - "-t", "dsa", // - "-P", "", // - "-C", comment, // - "-f", dsa.getAbsolutePath() // - }).waitFor(); - - } else { - // Generate the SSH daemon host key ourselves. This is complex - // because SimpleGeneratorHostKeyProvider doesn't mark the data - // file as only readable by us, exposing the private key for a - // short period of time. We try to reduce that risk by creating - // the key within a temporary directory. - // - final File tmpdir = new File(etc_dir, "tmp.sshkeygen"); - if (!tmpdir.mkdir()) { - throw die("Cannot create directory " + tmpdir); - } - chmod(0600, tmpdir); - - final File tmpkey = new File(tmpdir, key.getName()); - final SimpleGeneratorHostKeyProvider p; - - System.err.print(" rsa(simple)..."); - System.err.flush(); - p = new SimpleGeneratorHostKeyProvider(); - p.setPath(tmpkey.getAbsolutePath()); - p.setAlgorithm("RSA"); - p.loadKeys(); // forces the key to generate. - chmod(0600, tmpkey); - - if (!tmpkey.renameTo(key)) { - throw die("Cannot rename " + tmpkey + " to " + key); - } - if (!tmpdir.delete()) { - throw die("Cannot delete " + tmpdir); - } - } - System.err.println(" done"); - } - } - - private void start() { - final String[] argv = {gerrit_sh.getAbsolutePath(), "start"}; - final Process proc; - try { - System.err.println("Executing " + argv[0] + " " + argv[1]); - proc = Runtime.getRuntime().exec(argv); - } catch (IOException e) { - System.err.println("error: cannot start Gerrit: " + e.getMessage()); - return; - } - - try { - proc.getOutputStream().close(); - } catch (IOException e) { - } - - IoUtil.copyWithThread(proc.getInputStream(), System.err); - IoUtil.copyWithThread(proc.getErrorStream(), System.err); - - for (;;) { - try { - final int rc = proc.waitFor(); - if (rc != 0) { - System.err.println("error: cannot start Gerrit: exit status " + rc); - } - break; - } catch (InterruptedException e) { - // retry - } - } - } - - private void openBrowser() throws IOException { - if (ui.isBatch()) { - return; - } - - String url = cfg.getString("httpd", null, "listenUrl"); - if (url == null) { - return; - } - - if (url.startsWith("proxy-")) { - url = url.substring("proxy-".length()); - } - - final URI uri; - try { - uri = toURI(url); - } catch (URISyntaxException e) { - System.err.println("error: invalid httpd.listenUrl: " + url); - return; - } - final String hostname = uri.getHost(); - final int port = portOf(uri); - - System.err.print("Waiting for server to start ... "); - System.err.flush(); - for (;;) { - final Socket s; - try { - s = new Socket(hostname, port); + System.err.println("Executing " + argv[0] + " " + argv[1]); + proc = Runtime.getRuntime().exec(argv); } catch (IOException e) { - try { - Thread.sleep(100); - } catch (InterruptedException ie) { - } - continue; + System.err.println("error: cannot start Gerrit: " + e.getMessage()); + return; } - s.close(); - break; - } - System.err.println("OK"); - url = cfg.getString("gerrit", null, "canonicalWebUrl"); - if (url == null || url.isEmpty()) { - url = uri.toString(); + try { + proc.getOutputStream().close(); + } catch (IOException e) { + } + + IoUtil.copyWithThread(proc.getInputStream(), System.err); + IoUtil.copyWithThread(proc.getErrorStream(), System.err); + + for (;;) { + try { + final int rc = proc.waitFor(); + if (rc != 0) { + System.err.println("error: cannot start Gerrit: exit status " + rc); + } + break; + } catch (InterruptedException e) { + // retry + } + } } - if (!url.endsWith("/")) { - url += "/"; - } - url += "#" + PageLinks.ADMIN_PROJECTS; - System.err.println("Opening browser ..."); - StartBrowser.openURL(url); + } - private static URI toURI(String url) throws URISyntaxException { - final URI u = new URI(url); - if (isAnyAddress(u)) { - // If the URL uses * it means all addresses on this system, use the - // current hostname instead in the returned URI. - // - final int s = url.indexOf('*'); - url = url.substring(0, s) + hostname() + url.substring(s + 1); - } - return new URI(url); + private SiteRun createSiteRun(final SiteInit init) { + return createSysInjector(init).getInstance(SiteRun.class); } - private static boolean isAnyAddress(final URI u) { - return u.getHost() == null - && (u.getAuthority().equals("*") || u.getAuthority().startsWith("*:")); - } - - private static int portOf(final URI uri) { - int port = uri.getPort(); - if (port < 0) { - port = "https".equals(uri.getScheme()) ? 443 : 80; - } - return port; - } - - private void inject() { - dbInjector = createDbInjector(SINGLE_USER); - sysInjector = createSysInjector(); - sysInjector.injectMembers(this); - } - - private Injector createSysInjector() { + private Injector createSysInjector(final SiteInit init) { final List modules = new ArrayList(); modules.add(new AbstractModule() { @Override protected void configure() { + bind(ConsoleUI.class).toInstance(init.ui); + bind(InitFlags.class).toInstance(init.flags); + bind(GitRepositoryManager.class); + bind(GitProjectImporter.class); } }); - return dbInjector.createChildInjector(modules); - } - - private static String version() { - return com.google.gerrit.common.Version.getVersion(); - } - - private static String username() { - return System.getProperty("user.name"); - } - - private static String hostname() { - return SystemReader.getInstance().getHostname(); - } - - private static boolean isLocal(final String hostname) { - try { - return InetAddress.getByName(hostname).isLoopbackAddress(); - } catch (UnknownHostException e) { - return false; - } - } - - private static String dnOf(String name) { - if (name != null) { - int p = name.indexOf("://"); - if (0 < p) { - name = name.substring(p + 3); - } - - p = name.indexOf("."); - if (0 < p) { - name = name.substring(p + 1); - name = "DC=" + name.replaceAll("\\.", ",DC="); - } else { - name = null; - } - } - return name; - } - - private static String domainOf(String name) { - if (name != null) { - int p = name.indexOf("://"); - if (0 < p) { - name = name.substring(p + 3); - } - p = name.indexOf("."); - if (0 < p) { - name = name.substring(p + 1); - } - } - return name; + return createDbInjector(SINGLE_USER).createChildInjector(modules); } private static void recursiveDelete(File path) { @@ -1042,170 +265,4 @@ public class Init extends SiteProgram { System.err.println("warn: Cannot remove " + path); } } - - private static void extract(final File dst, final Class sibling, - final String name) throws IOException { - final InputStream in = open(sibling, name); - if (in != null) { - copy(dst, in); - } - } - - private static InputStream open(final Class sibling, final String name) - throws IOException { - final URL u = sibling.getResource(name); - if (u == null) { - String pkg = sibling.getName(); - int end = pkg.lastIndexOf('.'); - if (0 < end) { - pkg = pkg.substring(0, end + 1); - pkg = pkg.replace('.', '/'); - } else { - pkg = ""; - } - System.err.println("warn: Cannot read " + pkg + name); - return null; - } - return u.openStream(); - } - - private static void copy(final File dst, final InputStream in) - throws FileNotFoundException, IOException { - try { - dst.getParentFile().mkdirs(); - final FileOutputStream out = new FileOutputStream(dst); - try { - final byte[] buf = new byte[4096]; - int n; - while (0 < (n = in.read(buf))) { - out.write(buf, 0, n); - } - } finally { - out.close(); - } - } finally { - in.close(); - } - } - - private class Section { - final String section; - - Section(final String section) { - this.section = section; - } - - String get(String name) { - return cfg.getString(section, null, name); - } - - void set(final String name, final String value) { - final ArrayList all = new ArrayList(); - all.addAll(Arrays.asList(cfg.getStringList(section, null, name))); - - if (value != null) { - if (all.size() == 0 || all.size() == 1) { - cfg.setString(section, null, name, value); - } else { - all.set(0, value); - cfg.setStringList(section, null, name, all); - } - - } else if (all.size() == 0) { - } else if (all.size() == 1) { - cfg.unset(section, null, name); - } else { - all.remove(0); - cfg.setStringList(section, null, name, all); - } - } - - void unset(String name) { - set(name, null); - } - - String string(final String title, final String name, final String dv) { - return string(title, name, dv, false); - } - - String string(final String title, final String name, final String dv, - final boolean nullIfDefault) { - final String ov = get(name); - String nv = ui.readString(ov != null ? ov : dv, "%s", title); - if (nullIfDefault && nv == dv) { - nv = null; - } - if (!eq(ov, nv)) { - set(name, nv); - } - return nv; - } - - File path(final String title, final String name, final String defValue) { - return resolve(string(title, name, defValue)); - } - - > T select(final String title, final String name, - final T defValue) { - return select(title, name, defValue, false); - } - - > T select(final String title, final String name, - final T defValue, final boolean nullIfDefault) { - final boolean set = get(name) != null; - T oldValue = ConfigUtil.getEnum(cfg, section, null, name, defValue); - T newValue = ui.readEnum(oldValue, "%s", title); - if (nullIfDefault && newValue == defValue) { - newValue = null; - } - if (!set || oldValue != newValue) { - if (newValue != null) { - set(name, newValue.name()); - } else { - unset(name); - } - } - return newValue; - } - - String password(final String username, final String password) { - final String ov = sec.getString(section, null, password); - - String user = sec.getString(section, null, username); - if (user == null) { - user = get(username); - } - - if (user == null) { - sec.unset(section, null, password); - return null; - } - - if (ov != null) { - // If the user already has a password stored, try to reuse it - // rather than prompting for a whole new one. - // - if (ui.isBatch() || !ui.yesno(false, "Change %s's password", user)) { - return ov; - } - } - - final String nv = ui.password("%s's password", user); - if (!eq(ov, nv)) { - if (nv != null) { - sec.setString(section, null, password, nv); - } else { - sec.unset(section, null, password); - } - } - return nv; - } - } - - private static boolean eq(final String a, final String b) { - if (a == null && b == null) { - return true; - } - return a != null ? a.equals(b) : false; - } } diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java index edc68ecadd..3613024a28 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java @@ -20,7 +20,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.main.GerritLauncher; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; @@ -92,17 +92,16 @@ public class JettyServer { } } - private final File sitePath; + private final SitePaths site; private final Server httpd; /** Location on disk where our WAR file was unpacked to. */ private Resource baseResource; @Inject - JettyServer(@GerritServerConfig final Config cfg, - @SitePath final File sitePath, final JettyEnv env) - throws MalformedURLException, IOException { - this.sitePath = sitePath; + JettyServer(@GerritServerConfig final Config cfg, final SitePaths site, + final JettyEnv env) throws MalformedURLException, IOException { + this.site = site; Handler app = makeContext(env, cfg); @@ -231,12 +230,7 @@ public class JettyServer { if (path == null || path.length() == 0) { path = def; } - - File loc = new File(path); - if (!loc.isAbsolute()) { - loc = new File(sitePath, path); - } - return loc; + return site.resolve(path); } private ThreadPool threadPool(Config cfg) { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java new file mode 100644 index 0000000000..f281908745 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Browser.java @@ -0,0 +1,92 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.inject.Inject; + +import org.eclipse.jgit.lib.Config; +import org.h2.util.StartBrowser; + +import java.io.IOException; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; + +/** Opens the user's web browser to the web UI. */ +public class Browser { + private final Config cfg; + + @Inject + Browser(final @GerritServerConfig Config cfg) { + this.cfg = cfg; + } + + public void open() throws IOException { + open(null /* root page */); + } + + public void open(final String link) throws IOException { + String url = cfg.getString("httpd", null, "listenUrl"); + if (url == null) { + return; + } + + if (url.startsWith("proxy-")) { + url = url.substring("proxy-".length()); + } + + final URI uri; + try { + uri = InitUtil.toURI(url); + } catch (URISyntaxException e) { + System.err.println("error: invalid httpd.listenUrl: " + url); + return; + } + final String hostname = uri.getHost(); + final int port = InitUtil.portOf(uri); + + System.err.print("Waiting for server to start ... "); + System.err.flush(); + for (;;) { + final Socket s; + try { + s = new Socket(hostname, port); + } catch (IOException e) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + } + continue; + } + s.close(); + break; + } + System.err.println("OK"); + + url = cfg.getString("gerrit", null, "canonicalWebUrl"); + if (url == null || url.isEmpty()) { + url = uri.toString(); + } + if (!url.endsWith("/")) { + url += "/"; + } + if (link != null && !link.isEmpty()) { + url += "#" + link; + } + System.err.println("Opening browser ..."); + StartBrowser.openURL(url); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java new file mode 100644 index 0000000000..148bcb7ad5 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java @@ -0,0 +1,84 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.dnOf; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.reviewdb.AuthType; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** Initialize the {@code auth} configuration section. */ +@Singleton +class InitAuth implements InitStep { + private final ConsoleUI ui; + private final Section auth; + private final Section ldap; + + @Inject + InitAuth(final ConsoleUI ui, final Section.Factory sections) { + this.ui = ui; + this.auth = sections.get("auth"); + this.ldap = sections.get("ldap"); + } + + public void run() { + ui.header("User Authentication"); + + final AuthType auth_type = + auth.select("Authentication method", "type", AuthType.OPENID); + + switch (auth_type) { + case HTTP: + case HTTP_LDAP: { + String hdr = auth.get("httpHeader"); + if (ui.yesno(hdr != null, "Get username from custom HTTP header")) { + auth.string("Username HTTP header", "httpHeader", "SM_USER"); + } else if (hdr != null) { + auth.unset("httpHeader"); + } + auth.string("SSO logout URL", "logoutUrl", null); + break; + } + } + + switch (auth_type) { + case LDAP: + case HTTP_LDAP: { + String server = + ldap.string("LDAP server", "server", "ldap://localhost"); + if (server != null // + && !server.startsWith("ldap://") // + && !server.startsWith("ldaps://")) { + if (ui.yesno(false, "Use SSL")) { + server = "ldaps://" + server; + } else { + server = "ldap://" + server; + } + ldap.set("server", server); + } + + ldap.string("LDAP username", "username", null); + ldap.password("username", "password"); + + final String def_dn = dnOf(server); + String aBase = ldap.string("Account BaseDN", "accountBase", def_dn); + String gBase = ldap.string("Group BaseDN", "groupBase", aBase); + break; + } + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java new file mode 100644 index 0000000000..fb1a924c8f --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java @@ -0,0 +1,57 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.die; + +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; + +/** Initialize the {@code cache} configuration section. */ +@Singleton +class InitCache implements InitStep { + private final SitePaths site; + private final Section cache; + + @Inject + InitCache(final SitePaths site, final Section.Factory sections) { + this.site = site; + this.cache = sections.get("cache"); + } + + public void run() { + String path = cache.get("directory"); + + if (path != null && path.isEmpty()) { + // Explicitly set to empty implies the administrator has + // disabled the on disk cache and doesn't want it enabled. + // + return; + } + + if (path == null) { + path = "cache"; + cache.set("directory", path); + } + + final File loc = site.resolve(path); + if (!loc.exists() && !loc.mkdirs()) { + throw die("cannot create cache.directory " + loc.getAbsolutePath()); + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java new file mode 100644 index 0000000000..749eda28c6 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java @@ -0,0 +1,95 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.copy; +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.username; + +import com.google.gerrit.main.GerritLauncher; +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** Initialize the {@code container} configuration section. */ +@Singleton +class InitContainer implements InitStep { + private final ConsoleUI ui; + private final SitePaths site; + private final Section container; + + @Inject + InitContainer(final ConsoleUI ui, final SitePaths site, + final Section.Factory sections) { + this.ui = ui; + this.site = site; + this.container = sections.get("container"); + } + + public void run() throws FileNotFoundException, IOException { + ui.header("Container Process"); + + container.string("Run as", "user", username()); + container.string("Java runtime", "javaHome", javaHome()); + + File myWar; + try { + myWar = GerritLauncher.getDistributionArchive(); + } catch (FileNotFoundException e) { + System.err.println("warn: Cannot find gerrit.war"); + myWar = null; + } + + String path = container.get("war"); + if (path != null) { + path = container.string("Gerrit runtime", "war", // + myWar != null ? myWar.getAbsolutePath() : null); + if (path == null || path.isEmpty()) { + throw die("container.war is required"); + } + + } else if (myWar != null) { + final boolean copy; + final File siteWar = site.gerrit_war; + if (siteWar.exists()) { + copy = ui.yesno(true, "Upgrade %s", siteWar.getPath()); + } else { + copy = ui.yesno(true, "Copy gerrit.war to %s", siteWar.getPath()); + if (copy) { + container.unset("war"); + } else { + container.set("war", myWar.getAbsolutePath()); + } + } + if (copy) { + if (!ui.isBatch()) { + System.err.format("Copying gerrit.war to %s", siteWar.getPath()); + System.err.println(); + } + copy(siteWar, new FileInputStream(myWar)); + } + } + } + + private static String javaHome() { + return System.getProperty("java.home"); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java new file mode 100644 index 0000000000..ff94df1c07 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java @@ -0,0 +1,105 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.username; +import static com.google.gerrit.pgm.util.DataSourceProvider.Type.H2; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.pgm.util.DataSourceProvider; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; + +/** Initialize the {@code database} configuration section. */ +@Singleton +class InitDatabase implements InitStep { + private final ConsoleUI ui; + private final SitePaths site; + private final Libraries libraries; + private final Section database; + + @Inject + InitDatabase(final ConsoleUI ui, final SitePaths site, final Libraries libraries, + final Section.Factory sections) { + this.ui = ui; + this.site = site; + this.libraries = libraries; + this.database = sections.get("database"); + } + + public void run() { + ui.header("SQL Database"); + + final DataSourceProvider.Type db_type = + database.select("Database server type", "type", H2); + + switch (db_type) { + case MYSQL: + libraries.mysqlDriver.downloadRequired(); + break; + } + + final boolean userPassAuth; + switch (db_type) { + case H2: { + userPassAuth = false; + String path = database.get("database"); + if (path == null) { + path = "db/ReviewDB"; + database.set("database", path); + } + File db = site.resolve(path); + if (db == null) { + throw die("database.database must be supplied for H2"); + } + db = db.getParentFile(); + if (!db.exists() && !db.mkdirs()) { + throw die("cannot create database.database " + db.getAbsolutePath()); + } + break; + } + + case JDBC: { + userPassAuth = true; + database.string("Driver class name", "driver", null); + database.string("URL", "url", null); + break; + } + + case POSTGRES: + case POSTGRESQL: + case MYSQL: { + userPassAuth = true; + final String defPort = "(" + db_type.toString() + " default)"; + database.string("Server hostname", "hostname", "localhost"); + database.string("Server port", "port", defPort, true); + database.string("Database name", "database", "reviewdb"); + break; + } + + default: + throw die("internal bug, database " + db_type + " not supported"); + } + + if (userPassAuth) { + database.string("Database username", "username", username()); + database.password("username", "password"); + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java new file mode 100644 index 0000000000..5ca7e4131f --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java @@ -0,0 +1,49 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.FileBasedConfig; + +import java.io.IOException; + +/** Global variables used by the 'init' command. */ +@Singleton +public class InitFlags { + /** Recursively delete the site path if initialization fails. */ + public boolean deleteOnFailure; + + /** Run the Git project importer after initialization. */ + public boolean importProjects; + + /** Run the daemon (and open the web UI in a browser) after initialization. */ + public boolean autoStart; + + public final FileBasedConfig cfg; + public final FileBasedConfig sec; + + @Inject + InitFlags(final SitePaths site) throws IOException, ConfigInvalidException { + cfg = new FileBasedConfig(site.gerrit_config); + sec = new FileBasedConfig(site.secure_config); + + cfg.load(); + sec.load(); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java new file mode 100644 index 0000000000..2d9557799c --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java @@ -0,0 +1,55 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.die; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; + +/** Initialize the GitRepositoryManager configuration section. */ +@Singleton +class InitGitManager implements InitStep { + private final InitFlags flags; + private final ConsoleUI ui; + private final Section gerrit; + + @Inject + InitGitManager(final InitFlags flags, final ConsoleUI ui, + final Section.Factory sections) { + this.flags = flags; + this.ui = ui; + this.gerrit = sections.get("gerrit"); + } + + public void run() { + ui.header("Git Repositories"); + + File d = gerrit.path("Location of Git repositories", "basePath", "git"); + if (d == null) { + throw die("gerrit.basePath is required"); + } + if (d.exists()) { + if (!flags.importProjects && d.list() != null && d.list().length > 0) { + flags.importProjects = ui.yesno(true, "Import existing repositories"); + } + } else if (!d.mkdirs()) { + throw die("Cannot create " + d); + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java new file mode 100644 index 0000000000..8b4803f7d1 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java @@ -0,0 +1,203 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.chmod; +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.domainOf; +import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress; +import static com.google.gerrit.pgm.init.InitUtil.toURI; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.reviewdb.AuthType; +import com.google.gerrit.server.config.ConfigUtil; +import com.google.gerrit.server.config.SitePaths; +import com.google.gwtjsonrpc.server.SignedToken; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +/** Initialize the {@code httpd} configuration section. */ +@Singleton +class InitHttpd implements InitStep { + private final ConsoleUI ui; + private final SitePaths site; + private final InitFlags flags; + private final Section httpd; + private final Section gerrit; + + @Inject + InitHttpd(final ConsoleUI ui, final SitePaths site, final InitFlags flags, + final Section.Factory sections) { + this.ui = ui; + this.site = site; + this.flags = flags; + this.httpd = sections.get("httpd"); + this.gerrit = sections.get("gerrit"); + } + + public void run() throws IOException, InterruptedException { + ui.header("HTTP Daemon"); + + boolean proxy = false, ssl = false; + String address = "*"; + int port = -1; + String context = "/"; + String listenUrl = httpd.get("listenUrl"); + if (listenUrl != null && !listenUrl.isEmpty()) { + try { + final URI uri = toURI(listenUrl); + proxy = uri.getScheme().startsWith("proxy-"); + ssl = uri.getScheme().endsWith("https"); + address = isAnyAddress(new URI(listenUrl)) ? "*" : uri.getHost(); + port = uri.getPort(); + context = uri.getPath(); + } catch (URISyntaxException e) { + System.err.println("warning: invalid httpd.listenUrl " + listenUrl); + } + } + + proxy = ui.yesno(proxy, "Behind reverse proxy"); + + if (proxy) { + ssl = ui.yesno(ssl, "Proxy uses SSL (https://)"); + context = ui.readString(context, "Subdirectory on proxy server"); + } else { + ssl = ui.yesno(ssl, "Use SSL (https://)"); + context = "/"; + } + + address = ui.readString(address, "Listen on address"); + + if (port < 0) { + if (proxy) { + port = 8081; + } else if (ssl) { + port = 8443; + } else { + port = 8080; + } + } + port = ui.readInt(port, "Listen on port"); + + final StringBuilder urlbuf = new StringBuilder(); + urlbuf.append(proxy ? "proxy-" : ""); + urlbuf.append(ssl ? "https" : "http"); + urlbuf.append("://"); + urlbuf.append(address); + if (0 <= port) { + urlbuf.append(":"); + urlbuf.append(port); + } + urlbuf.append(context); + httpd.set("listenUrl", urlbuf.toString()); + + URI uri; + try { + uri = toURI(httpd.get("listenUrl")); + if (uri.getScheme().startsWith("proxy-")) { + // If its a proxy URL, assume the reverse proxy is on our system + // at the protocol standard ports (so omit the ports from the URL). + // + String s = uri.getScheme().substring("proxy-".length()); + uri = new URI(s + "://" + uri.getHost() + uri.getPath()); + } + } catch (URISyntaxException e) { + throw die("invalid httpd.listenUrl"); + } + if (gerrit.get("canonicalWebUrl") != null // + || (!proxy && ssl) // + || getAuthType() == AuthType.OPENID) { + gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString()); + } + + generateSslCertificate(); + } + + private void generateSslCertificate() throws IOException, + InterruptedException { + final String listenUrl = httpd.get("listenUrl"); + + if (!listenUrl.startsWith("https://")) { + // We aren't responsible for SSL processing. + // + return; + } + + String hostname; + try { + String url = gerrit.get("canonicalWebUrl"); + if (url == null || url.isEmpty()) { + url = listenUrl; + } + hostname = toURI(url).getHost(); + } catch (URISyntaxException e) { + System.err.println("Invalid httpd.listenUrl, not checking certificate"); + return; + } + + final File store = site.ssl_keystore; + if (!ui.yesno(!store.exists(), "Create new self-signed SSL certificate")) { + return; + } + + String ssl_pass = flags.sec.getString("http", null, "sslKeyPassword"); + if (ssl_pass == null || ssl_pass.isEmpty()) { + ssl_pass = SignedToken.generateRandomKey(); + flags.sec.setString("httpd", null, "sslKeyPassword", ssl_pass); + } + + hostname = ui.readString(hostname, "Certificate server name"); + final String validity = + ui.readString("365", "Certificate expires in (days)"); + + final String dname = + "CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname); + + final File tmpdir = new File(site.etc_dir, "tmp.sslcertgen"); + if (!tmpdir.mkdir()) { + throw die("Cannot create directory " + tmpdir); + } + chmod(0600, tmpdir); + + final File tmpstore = new File(tmpdir, "keystore"); + Runtime.getRuntime().exec(new String[] {"keytool", // + "-keystore", tmpstore.getAbsolutePath(), // + "-storepass", ssl_pass, // + "-genkeypair", // + "-alias", hostname, // + "-keyalg", "RSA", // + "-validity", validity, // + "-dname", dname, // + "-keypass", ssl_pass, // + }).waitFor(); + chmod(0600, tmpstore); + + if (!tmpstore.renameTo(store)) { + throw die("Cannot rename " + tmpstore + " to " + store); + } + if (!tmpdir.delete()) { + throw die("Cannot delete " + tmpdir); + } + } + + private AuthType getAuthType() { + return ConfigUtil.getEnum(flags.cfg, "auth", null, "type", AuthType.OPENID); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java new file mode 100644 index 0000000000..a55cea5124 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java @@ -0,0 +1,52 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.server.config.FactoryModule; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.internal.UniqueAnnotations; + +import java.lang.annotation.Annotation; + +/** Injection configuration for the site initialization process. */ +public class InitModule extends FactoryModule { + @Override + protected void configure() { + bind(SitePaths.class); + bind(InitFlags.class); + bind(Libraries.class); + bind(LibraryDownloader.class); + factory(Section.Factory.class); + + // Steps are executed in the order listed here. + // + step().to(UpgradeFrom2_0_x.class); + + step().to(InitGitManager.class); + step().to(InitDatabase.class); + step().to(InitAuth.class); + step().to(InitSendEmail.class); + step().to(InitContainer.class); + step().to(InitSshd.class); + step().to(InitHttpd.class); + step().to(InitCache.class); + } + + protected LinkedBindingBuilder step() { + final Annotation id = UniqueAnnotations.create(); + return bind(InitStep.class).annotatedWith(id); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java new file mode 100644 index 0000000000..7a3556eb3e --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java @@ -0,0 +1,58 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.isLocal; +import static com.google.gerrit.pgm.init.InitUtil.username; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.mail.SmtpEmailSender.Encryption; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** Initialize the {@code sendemail} configuration section. */ +@Singleton +class InitSendEmail implements InitStep { + private final ConsoleUI ui; + private final Section sendemail; + + @Inject + InitSendEmail(final ConsoleUI ui, final SitePaths site, + final Section.Factory sections) { + this.ui = ui; + this.sendemail = sections.get("sendemail"); + } + + public void run() { + ui.header("Email Delivery"); + + final String hostname = + sendemail.string("SMTP server hostname", "smtpServer", "localhost"); + + sendemail.string("SMTP server port", "smtpServerPort", "(default)", true); + + final Encryption enc = + sendemail.select("SMTP encryption", "smtpEncryption", Encryption.NONE, + true); + + String username = null; + if ((enc != null && enc != Encryption.NONE) || !isLocal(hostname)) { + username = username(); + } + sendemail.string("SMTP username", "smtpUser", username); + sendemail.password("smtpUser", "smtpPass"); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java new file mode 100644 index 0000000000..54a9760c32 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java @@ -0,0 +1,142 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.chmod; +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.hostname; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.util.SocketUtil; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.apache.sshd.common.util.SecurityUtils; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +/** Initialize the {@code sshd} configuration section. */ +@Singleton +class InitSshd implements InitStep { + private final ConsoleUI ui; + private final SitePaths site; + private final Libraries libraries; + private final Section sshd; + + @Inject + InitSshd(final ConsoleUI ui, final SitePaths site, final Libraries libraries, + final Section.Factory sections) { + this.ui = ui; + this.site = site; + this.libraries = libraries; + this.sshd = sections.get("sshd"); + } + + public void run() throws Exception { + ui.header("SSH Daemon"); + + String hostname = "*"; + int port = 29418; + String listenAddress = sshd.get("listenAddress"); + if (listenAddress != null && !listenAddress.isEmpty()) { + final InetSocketAddress addr = SocketUtil.parse(listenAddress, port); + hostname = SocketUtil.hostname(addr); + port = addr.getPort(); + } + + hostname = ui.readString(hostname, "Listen on address"); + port = ui.readInt(port, "Listen on port"); + sshd.set("listenAddress", SocketUtil.format(hostname, port)); + + if (site.ssh_rsa.exists() || site.ssh_dsa.exists()) { + libraries.bouncyCastle.downloadRequired(); + } else { + libraries.bouncyCastle.downloadOptional(); + } + + generateSshHostKeys(); + } + + private void generateSshHostKeys() throws InterruptedException, IOException { + if (!site.ssh_key.exists() // + && !site.ssh_rsa.exists() // + && !site.ssh_dsa.exists()) { + System.err.print("Generating SSH host key ..."); + System.err.flush(); + + if (SecurityUtils.isBouncyCastleRegistered()) { + // Generate the SSH daemon host key using ssh-keygen. + // + final String comment = "gerrit-code-review@" + hostname(); + + System.err.print(" rsa..."); + System.err.flush(); + Runtime.getRuntime().exec(new String[] {"ssh-keygen", // + "-q" /* quiet */, // + "-t", "rsa", // + "-P", "", // + "-C", comment, // + "-f", site.ssh_rsa.getAbsolutePath() // + }).waitFor(); + + System.err.print(" dsa..."); + System.err.flush(); + Runtime.getRuntime().exec(new String[] {"ssh-keygen", // + "-q" /* quiet */, // + "-t", "dsa", // + "-P", "", // + "-C", comment, // + "-f", site.ssh_dsa.getAbsolutePath() // + }).waitFor(); + + } else { + // Generate the SSH daemon host key ourselves. This is complex + // because SimpleGeneratorHostKeyProvider doesn't mark the data + // file as only readable by us, exposing the private key for a + // short period of time. We try to reduce that risk by creating + // the key within a temporary directory. + // + final File tmpdir = new File(site.etc_dir, "tmp.sshkeygen"); + if (!tmpdir.mkdir()) { + throw die("Cannot create directory " + tmpdir); + } + chmod(0600, tmpdir); + + final File tmpkey = new File(tmpdir, site.ssh_key.getName()); + final SimpleGeneratorHostKeyProvider p; + + System.err.print(" rsa(simple)..."); + System.err.flush(); + p = new SimpleGeneratorHostKeyProvider(); + p.setPath(tmpkey.getAbsolutePath()); + p.setAlgorithm("RSA"); + p.loadKeys(); // forces the key to generate. + chmod(0600, tmpkey); + + if (!tmpkey.renameTo(site.ssh_key)) { + throw die("Cannot rename " + tmpkey + " to " + site.ssh_key); + } + if (!tmpdir.delete()) { + throw die("Cannot delete " + tmpdir); + } + } + System.err.println(" done"); + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java new file mode 100644 index 0000000000..4fa3f906f1 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java @@ -0,0 +1,20 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +/** A single step in the site initialization process. */ +public interface InitStep { + public void run() throws Exception; +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java new file mode 100644 index 0000000000..fa1f35ab8e --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java @@ -0,0 +1,214 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.pgm.util.Die; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.LockFile; +import org.eclipse.jgit.util.SystemReader; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; + +/** Utility functions to help initialize a site. */ +class InitUtil { + static Die die(String why) { + return new Die(why); + } + + static Die die(String why, Throwable cause) { + return new Die(why, cause); + } + + static void savePublic(final FileBasedConfig sec) throws IOException { + sec.save(); + } + + static void saveSecure(final FileBasedConfig sec) throws IOException { + final byte[] out = Constants.encode(sec.toText()); + final File path = sec.getFile(); + final LockFile lf = new LockFile(path); + if (!lf.lock()) { + throw new IOException("Cannot lock " + path); + } + try { + chmod(0600, new File(path.getParentFile(), path.getName() + ".lock")); + lf.write(out); + if (!lf.commit()) { + throw new IOException("Cannot commit write to " + path); + } + } finally { + lf.unlock(); + } + } + + static void mkdir(final File path) { + if (!path.isDirectory() && !path.mkdir()) { + throw die("Cannot make directory " + path); + } + } + + static void chmod(final int mode, final File path) { + path.setReadable(false, false /* all */); + path.setWritable(false, false /* all */); + path.setExecutable(false, false /* all */); + + path.setReadable((mode & 0400) == 0400, true /* owner only */); + path.setWritable((mode & 0200) == 0200, true /* owner only */); + if (path.isDirectory() || (mode & 0100) == 0100) { + path.setExecutable(true, true /* owner only */); + } + + if ((mode & 0044) == 0044) { + path.setReadable(true, false /* all */); + } + if ((mode & 0011) == 0011) { + path.setExecutable(true, false /* all */); + } + } + + static String version() { + return com.google.gerrit.common.Version.getVersion(); + } + + static String username() { + return System.getProperty("user.name"); + } + + static String hostname() { + return SystemReader.getInstance().getHostname(); + } + + static boolean isLocal(final String hostname) { + try { + return InetAddress.getByName(hostname).isLoopbackAddress(); + } catch (UnknownHostException e) { + return false; + } + } + + static String dnOf(String name) { + if (name != null) { + int p = name.indexOf("://"); + if (0 < p) { + name = name.substring(p + 3); + } + + p = name.indexOf("."); + if (0 < p) { + name = name.substring(p + 1); + name = "DC=" + name.replaceAll("\\.", ",DC="); + } else { + name = null; + } + } + return name; + } + + static String domainOf(String name) { + if (name != null) { + int p = name.indexOf("://"); + if (0 < p) { + name = name.substring(p + 3); + } + p = name.indexOf("."); + if (0 < p) { + name = name.substring(p + 1); + } + } + return name; + } + + static void extract(final File dst, final Class sibling, + final String name) throws IOException { + final InputStream in = open(sibling, name); + if (in != null) { + copy(dst, in); + } + } + + private static InputStream open(final Class sibling, final String name) { + final InputStream in = sibling.getResourceAsStream(name); + if (in == null) { + String pkg = sibling.getName(); + int end = pkg.lastIndexOf('.'); + if (0 < end) { + pkg = pkg.substring(0, end + 1); + pkg = pkg.replace('.', '/'); + } else { + pkg = ""; + } + System.err.println("warn: Cannot read " + pkg + name); + return null; + } + return in; + } + + static void copy(final File dst, final InputStream in) + throws FileNotFoundException, IOException { + try { + dst.getParentFile().mkdirs(); + final FileOutputStream out = new FileOutputStream(dst); + try { + final byte[] buf = new byte[4096]; + int n; + while (0 < (n = in.read(buf))) { + out.write(buf, 0, n); + } + } finally { + out.close(); + } + } finally { + in.close(); + } + } + + static URI toURI(String url) throws URISyntaxException { + final URI u = new URI(url); + if (isAnyAddress(u)) { + // If the URL uses * it means all addresses on this system, use the + // current hostname instead in the returned URI. + // + final int s = url.indexOf('*'); + url = url.substring(0, s) + hostname() + url.substring(s + 1); + } + return new URI(url); + } + + static boolean isAnyAddress(final URI u) { + return u.getHost() == null + && (u.getAuthority().equals("*") || u.getAuthority().startsWith("*:")); + } + + static int portOf(final URI uri) { + int port = uri.getPort(); + if (port < 0) { + port = "https".equals(uri.getScheme()) ? 443 : 80; + } + return port; + } + + private InitUtil() { + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java similarity index 86% rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Libraries.java rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java index 8478e7e2c8..ff1eddfb49 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/Libraries.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.pgm.util; +package com.google.gerrit.pgm.init; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -27,19 +30,20 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; /** Standard {@link LibraryDownloader} instances derived from configuration. */ -public class Libraries { +@Singleton +class Libraries { private static final String RESOURCE_FILE = "com/google/gerrit/pgm/libraries.config"; - private final ConsoleUI ui; - private final File sitePath; + private final Provider downloadProvider; - public LibraryDownloader bouncyCastle; - public LibraryDownloader mysqlDriver; + /* final */LibraryDownloader bouncyCastle; + /* final */LibraryDownloader mysqlDriver; + + @Inject + Libraries(final Provider downloadProvider) { + this.downloadProvider = downloadProvider; - public Libraries(final ConsoleUI ui, final File sitePath) { - this.ui = ui; - this.sitePath = sitePath; init(); } @@ -70,7 +74,7 @@ public class Libraries { private void init(final Field field, final Config cfg) throws IllegalArgumentException, IllegalAccessException { final String n = field.getName(); - final LibraryDownloader dl = new LibraryDownloader(ui, sitePath); + final LibraryDownloader dl = downloadProvider.get(); dl.setName(get(cfg, n, "name")); dl.setJarUrl(get(cfg, n, "url")); dl.setSHA1(get(cfg, n, "sha1")); diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java similarity index 83% rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LibraryDownloader.java rename to gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java index aa9ebfda3b..ea1b515fb3 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LibraryDownloader.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java @@ -12,7 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.pgm.util; +package com.google.gerrit.pgm.init; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.pgm.util.Die; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.util.HttpSupport; @@ -32,38 +37,43 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** Get optional or required 3rd party library files into $site_path/lib. */ -public class LibraryDownloader { - private final ConsoleUI console; - private final File libDirectory; +class LibraryDownloader { + private final ConsoleUI ui; + private final File lib_dir; + private final ReloadSiteLibrary reload; + private boolean required; private String name; private String jarUrl; private String sha1; private File dst; - public LibraryDownloader(final ConsoleUI console, final File sitePath) { - this.console = console; - this.libDirectory = new File(sitePath, "lib"); + @Inject + LibraryDownloader(final ReloadSiteLibrary reload, final ConsoleUI ui, + final SitePaths site) { + this.ui = ui; + this.lib_dir = site.lib_dir; + this.reload = reload; } - public void setName(final String name) { + void setName(final String name) { this.name = name; } - public void setJarUrl(final String url) { + void setJarUrl(final String url) { this.jarUrl = url; } - public void setSHA1(final String sha1) { + void setSHA1(final String sha1) { this.sha1 = sha1; } - public void downloadRequired() { + void downloadRequired() { this.required = true; download(); } - public void downloadOptional() { + void downloadOptional() { this.required = false; download(); } @@ -82,14 +92,14 @@ public class LibraryDownloader { name = jarName; } - dst = new File(libDirectory, jarName); + dst = new File(lib_dir, jarName); if (!dst.exists() && shouldGet()) { doGet(); } } private boolean shouldGet() { - if (console.isBatch()) { + if (ui.isBatch()) { return required; } else { @@ -103,13 +113,13 @@ public class LibraryDownloader { msg.append(" in the library, but will also function without it.\n"); } msg.append("Download and install it now"); - return console.yesno(true, msg.toString(), name); + return ui.yesno(true, msg.toString(), name); } } private void doGet() { - if (!libDirectory.exists() && !libDirectory.mkdirs()) { - throw new Die("Cannot create " + libDirectory); + if (!lib_dir.exists() && !lib_dir.mkdirs()) { + throw new Die("Cannot create " + lib_dir); } try { @@ -118,7 +128,7 @@ public class LibraryDownloader { } catch (IOException err) { dst.delete(); - if (console.isBatch()) { + if (ui.isBatch()) { throw new Die("error: Cannot get " + jarUrl, err); } @@ -135,15 +145,17 @@ public class LibraryDownloader { System.err.println(); System.err.flush(); - console.waitForUser(); + ui.waitForUser(); if (dst.exists()) { verifyFileChecksum(); - } else if (!console.yesno(!required, "Continue without this library")) { + } else if (!ui.yesno(!required, "Continue without this library")) { throw new Die("aborted by user"); } } + + reload.reload(); } private void doGetByHttp() throws IOException { @@ -212,11 +224,11 @@ public class LibraryDownloader { System.err.println("Checksum " + dst.getName() + " OK"); System.err.flush(); - } else if (console.isBatch()) { + } else if (ui.isBatch()) { dst.delete(); throw new Die(dst + " SHA-1 checksum does not match"); - } else if (!console.yesno(null /* force an answer */, + } else if (!ui.yesno(null /* force an answer */, "error: SHA-1 checksum does not match\n" + "Use %s anyway",// dst.getName())) { dst.delete(); diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ReloadSiteLibrary.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ReloadSiteLibrary.java new file mode 100644 index 0000000000..b21d3c0a0b --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ReloadSiteLibrary.java @@ -0,0 +1,20 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +/** Requests the site's {@code lib/} directory be scanned again. */ +public interface ReloadSiteLibrary { + public void reload(); +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java new file mode 100644 index 0000000000..005904c269 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java @@ -0,0 +1,167 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.ConfigUtil; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; + +/** Helper to edit a section of the configuration files. */ +class Section { + interface Factory { + Section get(String name); + } + + private final InitFlags flags; + private final SitePaths site; + private final ConsoleUI ui; + private final String section; + + @Inject + Section(final InitFlags flags, final SitePaths site, final ConsoleUI ui, + @Assisted final String section) { + this.flags = flags; + this.site = site; + this.ui = ui; + this.section = section; + } + + String get(String name) { + return flags.cfg.getString(section, null, name); + } + + void set(final String name, final String value) { + final ArrayList all = new ArrayList(); + all.addAll(Arrays.asList(flags.cfg.getStringList(section, null, name))); + + if (value != null) { + if (all.size() == 0 || all.size() == 1) { + flags.cfg.setString(section, null, name, value); + } else { + all.set(0, value); + flags.cfg.setStringList(section, null, name, all); + } + + } else if (all.size() == 0) { + } else if (all.size() == 1) { + flags.cfg.unset(section, null, name); + } else { + all.remove(0); + flags.cfg.setStringList(section, null, name, all); + } + } + + > void set(final String name, final T value) { + if (value != null) { + set(name, value.name()); + } else { + unset(name); + } + } + + void unset(String name) { + set(name, (String) null); + } + + String string(final String title, final String name, final String dv) { + return string(title, name, dv, false); + } + + String string(final String title, final String name, final String dv, + final boolean nullIfDefault) { + final String ov = get(name); + String nv = ui.readString(ov != null ? ov : dv, "%s", title); + if (nullIfDefault && nv == dv) { + nv = null; + } + if (!eq(ov, nv)) { + set(name, nv); + } + return nv; + } + + File path(final String title, final String name, final String defValue) { + return site.resolve(string(title, name, defValue)); + } + + > T select(final String title, final String name, + final T defValue) { + return select(title, name, defValue, false); + } + + > T select(final String title, final String name, + final T defValue, final boolean nullIfDefault) { + final boolean set = get(name) != null; + T oldValue = ConfigUtil.getEnum(flags.cfg, section, null, name, defValue); + T newValue = ui.readEnum(oldValue, "%s", title); + if (nullIfDefault && newValue == defValue) { + newValue = null; + } + if (!set || oldValue != newValue) { + if (newValue != null) { + set(name, newValue); + } else { + unset(name); + } + } + return newValue; + } + + String password(final String username, final String password) { + final String ov = flags.sec.getString(section, null, password); + + String user = flags.sec.getString(section, null, username); + if (user == null) { + user = get(username); + } + + if (user == null) { + flags.sec.unset(section, null, password); + return null; + } + + if (ov != null) { + // If the user already has a password stored, try to reuse it + // rather than prompting for a whole new one. + // + if (ui.isBatch() || !ui.yesno(false, "Change %s's password", user)) { + return ov; + } + } + + final String nv = ui.password("%s's password", user); + if (!eq(ov, nv)) { + if (nv != null) { + flags.sec.setString(section, null, password, nv); + } else { + flags.sec.unset(section, null, password); + } + } + return nv; + } + + private static boolean eq(final String a, final String b) { + if (a == null && b == null) { + return true; + } + return a != null ? a.equals(b) : false; + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java new file mode 100644 index 0000000000..74e754830c --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java @@ -0,0 +1,101 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.chmod; +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.extract; +import static com.google.gerrit.pgm.init.InitUtil.mkdir; +import static com.google.gerrit.pgm.init.InitUtil.savePublic; +import static com.google.gerrit.pgm.init.InitUtil.saveSecure; +import static com.google.gerrit.pgm.init.InitUtil.version; + +import com.google.gerrit.pgm.Init; +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Binding; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.TypeLiteral; + +import java.util.ArrayList; +import java.util.List; + +/** Initialize (or upgrade) an existing site. */ +public class SitePathInitializer { + private final ConsoleUI ui; + private final InitFlags flags; + private final SitePaths site; + private final List steps; + + @Inject + public SitePathInitializer(final Injector injector, final ConsoleUI ui, + final InitFlags flags, final SitePaths site) { + this.ui = ui; + this.flags = flags; + this.site = site; + this.steps = stepsOf(injector); + } + + public void run() throws Exception { + ui.header("Gerrit Code Review %s", version()); + + if (site.isNew) { + if (!ui.yesno(true, "Create '%s'", site.site_path.getCanonicalPath())) { + throw die("aborted by user"); + } + if (!site.site_path.isDirectory() && !site.site_path.mkdirs()) { + throw die("Cannot make directory " + site.site_path); + } + flags.deleteOnFailure = true; + } + + mkdir(site.bin_dir); + mkdir(site.etc_dir); + mkdir(site.lib_dir); + mkdir(site.logs_dir); + mkdir(site.static_dir); + + for (InitStep step : steps) { + step.run(); + } + + savePublic(flags.cfg); + saveSecure(flags.sec); + + if (!site.replication_config.exists()) { + site.replication_config.createNewFile(); + } + + extract(site.gerrit_sh, Init.class, "gerrit.sh"); + chmod(0755, site.gerrit_sh); + + if (!ui.isBatch()) { + System.err.println(); + } + } + + private static List stepsOf(final Injector injector) { + final ArrayList r = new ArrayList(); + for (Binding b : all(injector)) { + r.add(b.getProvider().get()); + } + return r; + } + + private static List> all(final Injector injector) { + return injector.findBindingsByType(new TypeLiteral() {}); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java new file mode 100644 index 0000000000..842f28d688 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java @@ -0,0 +1,290 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import static com.google.gerrit.pgm.init.InitUtil.die; +import static com.google.gerrit.pgm.init.InitUtil.savePublic; +import static com.google.gerrit.pgm.init.InitUtil.saveSecure; + +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.pgm.util.DataSourceProvider; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.util.SocketUtil; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.FileBasedConfig; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; + +/** Upgrade from a 2.0.x site to a 2.1 site. */ +@Singleton +class UpgradeFrom2_0_x implements InitStep { + private static final String[] etcFiles = {"gerrit.config", // + "secure.config", // + "replication.config", // + "ssh_host_rsa_key", // + "ssh_host_rsa_key.pub", // + "ssh_host_dsa_key", // + "ssh_host_dsa_key.pub", // + "ssh_host_key", // + "contact_information.pub", // + "gitweb_config.perl", // + "keystore", // + "GerritSite.css", // + "GerritSiteFooter.html", // + "GerritSiteHeader.html", // + }; + + private final ConsoleUI ui; + + private final FileBasedConfig cfg; + private final FileBasedConfig sec; + private final File site_path; + private final File etc_dir; + private final Section.Factory sections; + + @Inject + UpgradeFrom2_0_x(final SitePaths site, final InitFlags flags, + final ConsoleUI ui, final Section.Factory sections) { + this.ui = ui; + this.sections = sections; + + this.cfg = flags.cfg; + this.sec = flags.sec; + this.site_path = site.site_path; + this.etc_dir = site.etc_dir; + } + + public boolean isNeedUpgrade() { + for (String name : etcFiles) { + if (new File(site_path, name).exists()) { + return true; + } + } + return false; + } + + public void run() throws IOException, ConfigInvalidException { + if (!isNeedUpgrade()) { + return; + } + + if (!ui.yesno(true, "Upgrade '%s'", site_path.getCanonicalPath())) { + throw die("aborted by user"); + } + + for (String name : etcFiles) { + final File src = new File(site_path, name); + final File dst = new File(etc_dir, name); + if (src.exists()) { + if (dst.exists()) { + throw die("File " + src + " would overwrite " + dst); + } + if (!src.renameTo(dst)) { + throw die("Cannot rename " + src + " to " + dst); + } + } + } + + // We have to reload the configuration after the rename as + // the initial load pulled up an non-existent (and thus + // believed to be empty) file. + // + cfg.load(); + sec.load(); + + final Properties oldprop = readGerritServerProperties(); + if (oldprop != null) { + final Section database = sections.get("database"); + + String url = oldprop.getProperty("url"); + if (url != null && !convertUrl(database, url)) { + database.set("type", DataSourceProvider.Type.JDBC); + database.set("driver", oldprop.getProperty("driver")); + database.set("url", url); + } + + String username = oldprop.getProperty("user"); + if (username == null || username.isEmpty()) { + username = oldprop.getProperty("username"); + } + if (username != null && !username.isEmpty()) { + cfg.setString("database", null, "username", username); + } + + String password = oldprop.getProperty("password"); + if (password != null && !password.isEmpty()) { + sec.setString("database", null, "password", password); + } + } + + String[] values; + + values = cfg.getStringList("ldap", null, "password"); + cfg.unset("ldap", null, "password"); + sec.setStringList("ldap", null, "password", Arrays.asList(values)); + + values = cfg.getStringList("sendemail", null, "smtpPass"); + cfg.unset("sendemail", null, "smtpPass"); + sec.setStringList("sendemail", null, "smtpPass", Arrays.asList(values)); + + saveSecure(sec); + savePublic(cfg); + } + + private boolean convertUrl(final Section database, String url) + throws UnsupportedEncodingException { + String username = null; + String password = null; + + if (url.contains("?")) { + final int q = url.indexOf('?'); + for (String pair : url.substring(q + 1).split("&")) { + final int eq = pair.indexOf('='); + if (0 < eq) { + return false; + } + + String n = URLDecoder.decode(pair.substring(0, eq), "UTF-8"); + String v = URLDecoder.decode(pair.substring(eq + 1), "UTF-8"); + + if ("user".equals(n) || "username".equals(n)) { + username = v; + + } else if ("password".equals(n)) { + password = v; + + } else { + // There is a parameter setting we don't recognize, use the + // JDBC URL format instead to preserve the configuration. + // + return false; + } + } + url = url.substring(0, q); + } + + if (url.startsWith("jdbc:h2:file:")) { + url = url.substring("jdbc:h2:file:".length()); + database.set("type", DataSourceProvider.Type.H2); + database.set("database", url); + return true; + } + + if (url.startsWith("jdbc:postgresql://")) { + url = url.substring("jdbc:postgresql://".length()); + final int sl = url.indexOf('/'); + if (sl < 0) { + return false; + } + + final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0); + database.set("type", DataSourceProvider.Type.POSTGRESQL); + sethost(database, addr); + database.set("database", url.substring(sl + 1)); + setuser(database, username, password); + return true; + } + + if (url.startsWith("jdbc:postgresql:")) { + url = url.substring("jdbc:postgresql:".length()); + database.set("type", DataSourceProvider.Type.POSTGRESQL); + database.set("hostname", "localhost"); + database.set("database", url); + setuser(database, username, password); + return true; + } + + if (url.startsWith("jdbc:mysql://")) { + url = url.substring("jdbc:mysql://".length()); + final int sl = url.indexOf('/'); + if (sl < 0) { + return false; + } + + final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0); + database.set("type", DataSourceProvider.Type.MYSQL); + sethost(database, addr); + database.set("database", url.substring(sl + 1)); + setuser(database, username, password); + return true; + } + + return false; + } + + private void sethost(final Section database, final InetSocketAddress addr) { + database.set("hostname", SocketUtil.hostname(addr)); + if (0 < addr.getPort()) { + database.set("port", String.valueOf(addr.getPort())); + } + } + + private void setuser(final Section database, String username, String password) { + if (username != null && !username.isEmpty()) { + database.set("username", username); + } + if (password != null && !password.isEmpty()) { + sec.setString("database", null, "password", password); + } + } + + private Properties readGerritServerProperties() throws IOException { + final Properties srvprop = new Properties(); + final String name = System.getProperty("GerritServer"); + File path; + if (name != null) { + path = new File(name); + } else { + path = new File(site_path, "GerritServer.properties"); + if (!path.exists()) { + path = new File("GerritServer.properties"); + } + } + if (path.exists()) { + try { + final InputStream in = new FileInputStream(path); + try { + srvprop.load(in); + } finally { + in.close(); + } + } catch (IOException e) { + throw new IOException("Cannot read " + name, e); + } + final Properties dbprop = new Properties(); + for (final Map.Entry e : srvprop.entrySet()) { + final String key = (String) e.getKey(); + if (key.startsWith("database.")) { + dbprop.put(key.substring("database.".length()), e.getValue()); + } + } + return dbprop; + } else { + return null; + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java index 03b06d5021..1e93651646 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java @@ -70,6 +70,18 @@ public abstract class ConsoleUI { /** Prompt the user for a string, suggesting a default, and returning choice. */ public abstract String readString(String def, String fmt, Object... args); + /** Prompt the user for an integer value, suggesting a default. */ + public int readInt(int def, String fmt, Object... args) { + for (;;) { + String p = readString(String.valueOf(def), fmt, args); + try { + return Integer.parseInt(p.trim(), 10); + } catch (NumberFormatException e) { + System.err.println("error: Invalid integer format: " + p.trim()); + } + } + } + /** Prompt the user for a password, returning the string; null if blank. */ public abstract String password(String fmt, Object... args); diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/DataSourceProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/DataSourceProvider.java index c69cbd7802..2be4f24eba 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/DataSourceProvider.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/DataSourceProvider.java @@ -17,7 +17,7 @@ package com.google.gerrit.pgm.util; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwtorm.jdbc.SimpleDataSource; import com.google.inject.Inject; import com.google.inject.Provider; @@ -41,9 +41,9 @@ public final class DataSourceProvider implements Provider, private final DataSource ds; @Inject - DataSourceProvider(@SitePath final File sitePath, + DataSourceProvider(final SitePaths site, @GerritServerConfig final Config cfg, Context ctx) { - ds = open(sitePath, cfg, ctx); + ds = open(site, cfg, ctx); } @Override @@ -74,7 +74,7 @@ public final class DataSourceProvider implements Provider, DEFAULT, JDBC, POSTGRES, POSTGRESQL, H2, MYSQL; } - private DataSource open(final File sitePath, final Config cfg, + private DataSource open(final SitePaths site, final Config cfg, final Context context) { Type type = ConfigUtil.getEnum(cfg, "database", null, "type", Type.DEFAULT); String driver = optional(cfg, "driver"); @@ -151,14 +151,11 @@ public final class DataSourceProvider implements Provider, database = "db/ReviewDB"; } - File db = new File(database); - if (!db.isAbsolute()) { - db = new File(sitePath, database); - try { - db = db.getCanonicalFile(); - } catch (IOException e) { - db = db.getAbsoluteFile(); - } + File db = site.resolve(database); + try { + db = db.getCanonicalFile(); + } catch (IOException e) { + db = db.getAbsoluteFile(); } url = pfx + db.toURI().toString(); } diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java index 462ee241fa..9d86984476 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java @@ -185,8 +185,12 @@ public abstract class SiteProgram extends AbstractProgram { } final StringBuilder buf = new StringBuilder(); - buf.append(why.getMessage()); - why = why.getCause(); + if (why != null) { + buf.append(why.getMessage()); + why = why.getCause(); + } else { + buf.append(first.getMessage()); + } while (why != null) { buf.append("\n caused by "); buf.append(why.toString()); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java index 8a3f41bb5d..5230ff6b53 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java @@ -20,7 +20,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import com.google.inject.ProvisionException; import com.google.inject.Singleton; @@ -31,9 +31,9 @@ import net.sf.ehcache.config.Configuration; import net.sf.ehcache.config.DiskStoreConfiguration; import net.sf.ehcache.store.MemoryStoreEvictionPolicy; +import org.eclipse.jgit.lib.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.eclipse.jgit.lib.Config; import java.io.File; import java.util.HashMap; @@ -64,16 +64,16 @@ public class CachePool { } private final Config config; - private final File sitePath; + private final SitePaths site; private final Object lock = new Object(); private final Map> caches; private CacheManager manager; @Inject - CachePool(@GerritServerConfig final Config cfg, @SitePath final File sitePath) { + CachePool(@GerritServerConfig final Config cfg, final SitePaths site) { this.config = cfg; - this.sitePath = sitePath; + this.site = site; this.caches = new HashMap>(); } @@ -198,16 +198,9 @@ public class CachePool { return; } - String path = config.getString("cache", null, "directory"); - if (path == null || path.length() == 0) { - return; - } - - File loc = new File(path); - if (!loc.isAbsolute()) { - loc = new File(sitePath, path); - } - if (loc.exists() || loc.mkdirs()) { + File loc = site.resolve(config.getString("cache", null, "directory")); + if (loc == null) { + } else if (loc.exists() || loc.mkdirs()) { if (loc.canWrite()) { final DiskStoreConfiguration c = new DiskStoreConfiguration(); c.setPath(loc.getAbsolutePath()); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java index 0ad6b9d934..9f2c80dd57 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigModule.java @@ -24,6 +24,7 @@ import org.eclipse.jgit.lib.Config; public class GerritServerConfigModule extends AbstractModule { @Override protected void configure() { + bind(SitePaths.class); bind(Config.class).annotatedWith(GerritServerConfig.class).toProvider( GerritServerConfigProvider.class).in(SINGLETON); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java index 6cced920f8..6592ac774d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritServerConfigProvider.java @@ -24,7 +24,6 @@ import org.eclipse.jgit.lib.FileBasedConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; /** Provides {@link Config} annotated with {@link GerritServerConfig}. */ @@ -32,23 +31,20 @@ class GerritServerConfigProvider implements Provider { private static final Logger log = LoggerFactory.getLogger(GerritServerConfigProvider.class); - private final File sitePath; + private final SitePaths site; @Inject - GerritServerConfigProvider(@SitePath final File path) { - sitePath = path; + GerritServerConfigProvider(final SitePaths site) { + this.site = site; } @Override public Config get() { - final File etc = new File(sitePath, "etc"); - final File gerrit_config = new File(etc, "gerrit.config"); - final File secure_config = new File(etc, "secure.config"); - - FileBasedConfig cfg = new FileBasedConfig(gerrit_config); + FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config); if (!cfg.getFile().exists()) { - log.info("No " + gerrit_config.getAbsolutePath() + "; assuming defaults"); + log.info("No " + site.gerrit_config.getAbsolutePath() + + "; assuming defaults"); return cfg; } @@ -60,8 +56,8 @@ class GerritServerConfigProvider implements Provider { throw new ProvisionException(e.getMessage(), e); } - if (secure_config.exists()) { - cfg = new FileBasedConfig(cfg, secure_config); + if (site.secure_config.exists()) { + cfg = new FileBasedConfig(cfg, site.secure_config); try { cfg.load(); } catch (IOException e) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java new file mode 100644 index 0000000000..52723e2a12 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SitePaths.java @@ -0,0 +1,113 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import java.io.File; +import java.io.FileNotFoundException; + +/** Important paths within a {@link SitePath}. */ +@Singleton +public final class SitePaths { + public final File site_path; + public final File bin_dir; + public final File etc_dir; + public final File lib_dir; + public final File logs_dir; + public final File static_dir; + + public final File gerrit_sh; + public final File gerrit_war; + + public final File gerrit_config; + public final File secure_config; + public final File replication_config; + public final File contact_information_pub; + + public final File ssl_keystore; + public final File ssh_key; + public final File ssh_rsa; + public final File ssh_dsa; + + public final File site_css; + public final File site_header; + public final File site_footer; + public final File site_gitweb; + + /** {@code true} if {@link #site_path} has not been initialized. */ + public final boolean isNew; + + @Inject + SitePaths(final @SitePath File sitePath) throws FileNotFoundException { + site_path = sitePath; + + bin_dir = new File(site_path, "bin"); + etc_dir = new File(site_path, "etc"); + lib_dir = new File(site_path, "lib"); + logs_dir = new File(site_path, "logs"); + static_dir = new File(site_path, "static"); + + gerrit_sh = new File(bin_dir, "gerrit.sh"); + gerrit_war = new File(bin_dir, "gerrit.war"); + + gerrit_config = new File(etc_dir, "gerrit.config"); + secure_config = new File(etc_dir, "secure.config"); + replication_config = new File(etc_dir, "replication.config"); + contact_information_pub = new File(etc_dir, "contact_information.pub"); + + ssl_keystore = new File(etc_dir, "keystore"); + ssh_key = new File(etc_dir, "ssh_host_key"); + ssh_rsa = new File(etc_dir, "ssh_host_rsa_key"); + ssh_dsa = new File(etc_dir, "ssh_host_dsa_key"); + + site_css = new File(etc_dir, "GerritSite.css"); + site_header = new File(etc_dir, "GerritSiteHeader.html"); + site_footer = new File(etc_dir, "GerritSiteFooter.html"); + site_gitweb = new File(etc_dir, "gitweb_config.perl"); + + if (site_path.exists()) { + final String[] contents = site_path.list(); + if (contents != null) + isNew = contents.length == 0; + else if (site_path.isDirectory()) + throw new FileNotFoundException("Cannot access " + site_path); + else + throw new FileNotFoundException("Not a directory: " + site_path); + } else { + isNew = true; + } + } + + /** + * Resolve an absolute or relative path. + *

+ * Relative paths are resolved relative to the {@link #site_path}. + * + * @param path the path string to resolve. May be null. + * @return the resolved path; null if {@code path} was null or empty. + */ + public File resolve(final String path) { + if (path != null && !path.isEmpty()) { + File loc = new File(path); + if (!loc.isAbsolute()) { + loc = new File(site_path, path); + } + return loc; + } + return null; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java index d32a5f22bc..da17c08581 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java @@ -16,7 +16,7 @@ package com.google.gerrit.server.contact; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwtorm.client.SchemaFactory; import com.google.inject.Inject; import com.google.inject.Provider; @@ -32,14 +32,14 @@ import java.net.URL; /** Creates the {@link ContactStore} based on the configuration. */ public class ContactStoreProvider implements Provider { private final Config config; - private final File sitePath; + private final SitePaths site; private final SchemaFactory schema; @Inject ContactStoreProvider(@GerritServerConfig final Config config, - @SitePath final File sitePath, final SchemaFactory schema) { + final SitePaths site, final SchemaFactory schema) { this.config = config; - this.sitePath = sitePath; + this.site = site; this.schema = schema; } @@ -63,7 +63,7 @@ public class ContactStoreProvider implements Provider { } final String storeAPPSEC = config.getString("contactstore", null, "appsec"); - final File pubkey = new File(sitePath, "etc/contact_information.pub"); + final File pubkey = site.contact_information_pub; if (!pubkey.exists()) { throw new ProvisionException("PGP public key file \"" + pubkey.getAbsolutePath() + "\" not found"); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java new file mode 100644 index 0000000000..64c0d2b8a8 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java @@ -0,0 +1,109 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.git; + +import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gerrit.reviewdb.Project.SubmitType; +import com.google.gwtorm.client.OrmException; +import com.google.gwtorm.client.SchemaFactory; +import com.google.inject.Inject; + +import org.eclipse.jgit.lib.RepositoryCache.FileKey; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** Imports all projects found within the repository manager. */ +public class GitProjectImporter { + public interface Messages { + void warning(String msg); + } + + private final GitRepositoryManager repositoryManager; + private final SchemaFactory schema; + private Messages messages; + + @Inject + GitProjectImporter(final GitRepositoryManager repositoryManager, + final SchemaFactory schema) { + this.repositoryManager = repositoryManager; + this.schema = schema; + } + + public void run(final Messages msg) throws OrmException, IOException { + messages = msg; + final ReviewDb db = schema.open(); + try { + final HashSet have = new HashSet(); + for (Project p : db.projects().all()) { + have.add(p.getName()); + } + importProjects(repositoryManager.getBasePath(), "", db, have); + } finally { + db.close(); + } + } + + private void importProjects(final File dir, final String prefix, + final ReviewDb db, final Set have) throws OrmException, + IOException { + final File[] ls = dir.listFiles(); + if (ls == null) { + return; + } + + for (File f : ls) { + String name = f.getName(); + if (".".equals(name) || "..".equals(name)) { + continue; + } + + if (FileKey.isGitRepository(f)) { + if (name.equals(".git")) { + name = prefix.substring(0, prefix.length() - 1); + + } else if (name.endsWith(".git")) { + name = prefix + name.substring(0, name.length() - 4); + + } else { + name = prefix + name; + messages.warning("Ignoring non-standard name '" + name + "'"); + continue; + } + + if (have.contains(name)) { + continue; + } + + final Project.NameKey nameKey = new Project.NameKey(name); + final Project.Id idKey = new Project.Id(db.nextProjectId()); + final Project p = new Project(nameKey, idKey); + + p.setDescription(repositoryManager.getProjectDescription(name)); + p.setSubmitType(SubmitType.MERGE_IF_NECESSARY); + p.setUseContributorAgreements(false); + p.setUseSignedOffBy(false); + db.projects().insert(Collections.singleton(p)); + + } else if (f.isDirectory()) { + importProjects(f, prefix + f.getName() + "/", db, have); + } + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java index 2d48568c5a..719f2405de 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java @@ -16,7 +16,7 @@ package com.google.gerrit.server.git; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -41,7 +41,8 @@ import java.io.IOException; /** Class managing Git repositories. */ @Singleton public class GitRepositoryManager { - private static final Logger log = LoggerFactory.getLogger(GitRepositoryManager.class); + private static final Logger log = + LoggerFactory.getLogger(GitRepositoryManager.class); private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; @@ -66,28 +67,20 @@ public class GitRepositoryManager { } } - private final File sitePath; - private final File basepath; + private final File basePath; @Inject - GitRepositoryManager(@SitePath final File path, @GerritServerConfig final Config cfg) { - sitePath = path; - - final String basePath = cfg.getString("gerrit", null, "basepath"); - if (basePath != null) { - File root = new File(basePath); - if (!root.isAbsolute()) { - root = new File(sitePath, basePath); - } - basepath = root; - } else { - basepath = null; + GitRepositoryManager(final SitePaths site, + @GerritServerConfig final Config cfg) { + basePath = site.resolve(cfg.getString("gerrit", null, "basePath")); + if (basePath == null) { + throw new IllegalStateException("gerrit.basePath must be configured"); } } /** @return base directory under which all projects are stored. */ public File getBasePath() { - return basepath; + return basePath; } /** @@ -101,16 +94,12 @@ public class GitRepositoryManager { */ public Repository openRepository(String name) throws RepositoryNotFoundException { - if (basepath == null) { - throw new RepositoryNotFoundException("No gerrit.basepath configured"); - } - if (isUnreasonableName(name)) { throw new RepositoryNotFoundException("Invalid name: " + name); } try { - final FileKey loc = FileKey.lenient(new File(basepath, name)); + final FileKey loc = FileKey.lenient(new File(basePath, name)); return RepositoryCache.open(loc); } catch (IOException e1) { final RepositoryNotFoundException e2; @@ -131,10 +120,6 @@ public class GitRepositoryManager { */ public Repository createRepository(String name) throws RepositoryNotFoundException { - if (basepath == null) { - throw new RepositoryNotFoundException("No gerrit.basepath configured"); - } - if (isUnreasonableName(name)) { throw new RepositoryNotFoundException("Invalid name: " + name); } @@ -143,7 +128,7 @@ public class GitRepositoryManager { if (!name.endsWith(".git")) { name = name + ".git"; } - final FileKey loc = FileKey.exact(new File(basepath, name)); + final FileKey loc = FileKey.exact(new File(basePath, name)); return RepositoryCache.open(loc, false); } catch (IOException e1) { final RepositoryNotFoundException e2; @@ -242,5 +227,4 @@ public class GitRepositoryManager { return false; // is a reasonable name } - } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java index 2b451b424f..3981223bea 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java @@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.ReplicationUser; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.ProjectControl; import com.google.gwtorm.client.OrmException; @@ -48,7 +48,6 @@ import org.eclipse.jgit.util.QuotedString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URISyntaxException; @@ -86,15 +85,14 @@ public class PushReplication implements ReplicationQueue { private final ReplicationUser.Factory replicationUserFactory; @Inject - PushReplication(final Injector i, final WorkQueue wq, - @SitePath final File sitePath, final ReplicationUser.Factory ruf, - final SchemaFactory db) throws ConfigInvalidException, - IOException { + PushReplication(final Injector i, final WorkQueue wq, final SitePaths site, + final ReplicationUser.Factory ruf, final SchemaFactory db) + throws ConfigInvalidException, IOException { injector = i; workQueue = wq; database = db; replicationUserFactory = ruf; - configs = allConfigs(sitePath); + configs = allConfigs(site); } @Override @@ -129,11 +127,9 @@ public class PushReplication implements ReplicationQueue { return pat.substring(0, n) + val + pat.substring(n + 3 + key.length()); } - private List allConfigs(final File path) + private List allConfigs(final SitePaths site) throws ConfigInvalidException, IOException { - final File etc = new File(path, "etc"); - final File cfgFile = new File(etc, "replication.config"); - final FileBasedConfig cfg = new FileBasedConfig(cfgFile); + final FileBasedConfig cfg = new FileBasedConfig(site.replication_config); if (!cfg.getFile().exists()) { log.warn("No " + cfg.getFile() + "; not replicating"); @@ -150,7 +146,8 @@ public class PushReplication implements ReplicationQueue { throw new ConfigInvalidException("Config file " + cfg.getFile() + " is invalid: " + e.getMessage(), e); } catch (IOException e) { - throw new IOException("Cannot read " + cfgFile + ": " + e.getMessage(), e); + throw new IOException("Cannot read " + cfg.getFile() + ": " + + e.getMessage(), e); } final List r = new ArrayList(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java index c11132241d..32e34ce0d6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java @@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.SchemaVersion; import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.WildProjectNameProvider; import com.google.gerrit.server.workflow.NoOpFunction; import com.google.gerrit.server.workflow.SubmitFunction; @@ -46,16 +47,20 @@ public class SchemaCreator { private static final Project.NameKey DEFAULT_WILD_NAME = new Project.NameKey("-- All Projects --"); - private final File sitePath; + private final @SitePath + File site_path; private final ScriptRunner index_generic; private final ScriptRunner index_postgres; private final ScriptRunner mysql_nextval; @Inject - public SchemaCreator(final @SitePath File sitePath) { - this.sitePath = sitePath; + public SchemaCreator(final SitePaths site) { + this(site.site_path); + } + public SchemaCreator(final @SitePath File site) { + site_path = site; index_generic = new ScriptRunner("index_generic.sql"); index_postgres = new ScriptRunner("index_postgres.sql"); mysql_nextval = new ScriptRunner("mysql_nextval.sql"); @@ -98,23 +103,23 @@ public class SchemaCreator { private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException { final AccountGroup admin = - new AccountGroup(new AccountGroup.NameKey("Administrators"), new AccountGroup.Id(c - .nextAccountGroupId())); + new AccountGroup(new AccountGroup.NameKey("Administrators"), + new AccountGroup.Id(c.nextAccountGroupId())); admin.setDescription("Gerrit Site Administrators"); admin.setType(AccountGroup.Type.INTERNAL); c.accountGroups().insert(Collections.singleton(admin)); final AccountGroup anonymous = - new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), new AccountGroup.Id(c - .nextAccountGroupId())); + new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), + new AccountGroup.Id(c.nextAccountGroupId())); anonymous.setDescription("Any user, signed-in or not"); anonymous.setOwnerGroupId(admin.getId()); anonymous.setType(AccountGroup.Type.SYSTEM); c.accountGroups().insert(Collections.singleton(anonymous)); final AccountGroup registered = - new AccountGroup(new AccountGroup.NameKey("Registered Users"), new AccountGroup.Id(c - .nextAccountGroupId())); + new AccountGroup(new AccountGroup.NameKey("Registered Users"), + new AccountGroup.Id(c.nextAccountGroupId())); registered.setDescription("Any signed-in user"); registered.setOwnerGroupId(admin.getId()); registered.setType(AccountGroup.Type.SYSTEM); @@ -126,9 +131,9 @@ public class SchemaCreator { s.anonymousGroupId = anonymous.getId(); s.registeredGroupId = registered.getId(); try { - s.sitePath = sitePath.getCanonicalPath(); + s.sitePath = site_path.getCanonicalPath(); } catch (IOException e) { - s.sitePath = sitePath.getAbsolutePath(); + s.sitePath = site_path.getAbsolutePath(); } c.systemConfig().insert(Collections.singleton(s)); return s; @@ -160,8 +165,8 @@ public class SchemaCreator { txn.commit(); } - private void initCodeReviewCategory(final ReviewDb c, final SystemConfig sConfig) - throws OrmException { + private void initCodeReviewCategory(final ReviewDb c, + final SystemConfig sConfig) throws OrmException { final Transaction txn = c.beginTransaction(); final ApprovalCategory cat; final ArrayList vals; @@ -203,7 +208,8 @@ public class SchemaCreator { txn.commit(); } - private void initReadCategory(final ReviewDb c, final SystemConfig sConfig) throws OrmException { + private void initReadCategory(final ReviewDb c, final SystemConfig sConfig) + throws OrmException { final Transaction txn = c.beginTransaction(); final ApprovalCategory cat; final ArrayList vals; @@ -269,14 +275,16 @@ public class SchemaCreator { cat.setFunctionName(NoOpFunction.NAME); vals = new ArrayList(); vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag")); - vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, "Create Annotated Tag")); + vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, + "Create Annotated Tag")); vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag")); c.approvalCategories().insert(Collections.singleton(cat), txn); c.approvalCategoryValues().insert(vals, txn); txn.commit(); } - private void initPushUpdateBranchCategory(final ReviewDb c) throws OrmException { + private void initPushUpdateBranchCategory(final ReviewDb c) + throws OrmException { final Transaction txn = c.beginTransaction(); final ApprovalCategory cat; final ArrayList vals; @@ -287,14 +295,16 @@ public class SchemaCreator { vals = new ArrayList(); vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch")); vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch")); - vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, "Force Push Branch; Delete Branch")); + vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, + "Force Push Branch; Delete Branch")); c.approvalCategories().insert(Collections.singleton(cat), txn); c.approvalCategoryValues().insert(vals, txn); txn.commit(); } - private static ApprovalCategoryValue value(final ApprovalCategory cat, final int value, - final String name) { - return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), (short) value), name); + private static ApprovalCategoryValue value(final ApprovalCategory cat, + final int value, final String name) { + return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), + (short) value), name); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java index 5ef73c3d59..b9daca27bc 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java @@ -17,27 +17,25 @@ package com.google.gerrit.server.schema; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.SchemaVersion; import com.google.gerrit.reviewdb.SystemConfig; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.SchemaFactory; import com.google.inject.Inject; -import java.io.File; import java.io.IOException; import java.util.Collections; /** Creates or updates the current database schema. */ public class SchemaUpdater { private final SchemaFactory schema; - private final File sitePath; + private final SitePaths site; private final SchemaCreator creator; @Inject - SchemaUpdater(final SchemaFactory schema, - final @SitePath File sitePath, + SchemaUpdater(final SchemaFactory schema, final SitePaths site, final SchemaCreator creator) { this.schema = schema; - this.sitePath = sitePath; + this.site = site; this.creator = creator; } @@ -70,9 +68,9 @@ public class SchemaUpdater { throw new OrmException("No record in system_config table"); } try { - sc.sitePath = sitePath.getCanonicalPath(); + sc.sitePath = site.site_path.getCanonicalPath(); } catch (IOException e) { - sc.sitePath = sitePath.getAbsolutePath(); + sc.sitePath = site.site_path.getAbsolutePath(); } db.systemConfig().update(Collections.singleton(sc)); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SocketUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SocketUtil.java new file mode 100644 index 0000000000..d7afd9597b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SocketUtil.java @@ -0,0 +1,123 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.util; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +public final class SocketUtil { + /** True if this InetAddress is a raw IPv6 in dotted quad notation. */ + public static boolean isIPv6(final InetAddress ip) { + return ip instanceof Inet6Address && ip.getHostName().equals(ip.getHostAddress()); + } + + /** Get the name or IP address, or {@code *} if this address is a wildcard IP. */ + public static String hostname(final InetSocketAddress addr) { + if (addr.getAddress() != null) { + if (addr.getAddress().isAnyLocalAddress()) { + return "*"; + } + return addr.getAddress().getHostName(); + } + return addr.getHostName(); + } + + /** Format an address string into {@code host:port} or {@code *:port} syntax. */ + public static String format(final SocketAddress s, final int defaultPort) { + if (s instanceof InetSocketAddress) { + final InetSocketAddress addr = (InetSocketAddress) s; + return format(hostname(addr), addr.getPort()); + } + return s.toString(); + } + + /** Format an address string into {@code host:port} or {@code *:port} syntax. */ + public static String format(String hostname, int port) { + if (0 <= hostname.indexOf(':')) { + hostname = "[" + hostname + "]"; + } + return hostname + ":" + port; + } + + /** Parse an address string such as {@code host:port} or {@code *:port}. */ + public static InetSocketAddress parse(final String desc, final int defaultPort) { + String hostStr; + String portStr; + + if (desc.startsWith("[")) { + // IPv6, as a raw IP address. + // + final int hostEnd = desc.indexOf(']'); + if (hostEnd < 0) { + throw new IllegalArgumentException("invalid IPv6 representation"); + } + + hostStr = desc.substring(1, hostEnd); + portStr = desc.substring(hostEnd + 1); + } else { + // IPv4, or a host name. + // + final int hostEnd = desc.indexOf(':'); + hostStr = 0 <= hostEnd ? desc.substring(0, hostEnd) : desc; + portStr = 0 <= hostEnd ? desc.substring(hostEnd) : ""; + } + + if ("".equals(hostStr)) { + hostStr = "*"; + } + if (portStr.startsWith(":")) { + portStr = portStr.substring(1); + } + + final int port; + if (portStr.length() > 0) { + try { + port = Integer.parseInt(portStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid port"); + } + } else { + port = defaultPort; + } + + if ("*".equals(hostStr)) { + return new InetSocketAddress(port); + } else { + return InetSocketAddress.createUnresolved(hostStr, port); + } + } + + /** Parse and resolve an address string, looking up the IP address. */ + public static InetSocketAddress resolve(final String desc, final int defaultPort) { + final InetSocketAddress addr = parse(desc, defaultPort); + if (addr.getAddress() != null && addr.getAddress().isAnyLocalAddress()) { + return addr; + + } else { + try { + final InetAddress host = InetAddress.getByName(addr.getHostName()); + return new InetSocketAddress(host, addr.getPort()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + } + + private SocketUtil() { + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java index 4651393a18..5e10f420ff 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/HostKeyProvider.java @@ -14,7 +14,7 @@ package com.google.gerrit.sshd; -import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.ProvisionException; @@ -22,26 +22,24 @@ import com.google.inject.ProvisionException; import org.apache.sshd.common.KeyPairProvider; import org.apache.sshd.common.keyprovider.FileKeyPairProvider; import org.apache.sshd.common.util.SecurityUtils; -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import java.io.File; import java.util.ArrayList; import java.util.List; class HostKeyProvider implements Provider { - private final File sitePath; + private final SitePaths site; @Inject - HostKeyProvider(@SitePath final File sitePath) { - this.sitePath = sitePath; + HostKeyProvider(final SitePaths site) { + this.site = site; } @Override public KeyPairProvider get() { - final File etc = new File(sitePath, "etc"); - final File anyKey = new File(etc, "ssh_host_key"); - final File rsaKey = new File(etc, "ssh_host_rsa_key"); - final File dsaKey = new File(etc, "ssh_host_dsa_key"); + final File anyKey = site.ssh_key; + final File rsaKey = site.ssh_rsa; + final File dsaKey = site.ssh_dsa; final List keys = new ArrayList(2); if (rsaKey.exists()) { @@ -60,18 +58,8 @@ class HostKeyProvider implements Provider { } if (keys.isEmpty()) { - // No administrator created host key? Generate and save our own. - // - final SimpleGeneratorHostKeyProvider keyp; - - if (!etc.exists() && !etc.mkdirs()) { - throw new ProvisionException("Cannot create directory " + etc); - } - keyp = new SimpleGeneratorHostKeyProvider(); - keyp.setPath(anyKey.getAbsolutePath()); - return keyp; + throw new ProvisionException("No SSH keys under " + site.etc_dir); } - if (!SecurityUtils.isBouncyCastleRegistered()) { throw new ProvisionException("Bouncy Castle Crypto not installed;" + " needed to read server host keys: " + keys + ""); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java index b3f97148c9..ec2c914b57 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java @@ -18,6 +18,7 @@ import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.ssh.SshInfo; import com.google.gerrit.server.util.IdGenerator; +import com.google.gerrit.server.util.SocketUtil; import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.Singleton; @@ -71,11 +72,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.net.UnknownHostException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.PublicKey; @@ -112,28 +110,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { private static final Logger log = LoggerFactory.getLogger(SshDaemon.class); - private static String format(final SocketAddress addr) { - if (addr instanceof InetSocketAddress) { - final InetSocketAddress inetAddr = (InetSocketAddress) addr; - final InetAddress hostAddr = inetAddr.getAddress(); - String host; - if (hostAddr.isAnyLocalAddress()) { - host = "*"; - } else if (inetAddr.getPort() == IANA_SSH_PORT && !isIPv6(hostAddr)) { - return inetAddr.getHostName(); - } else { - host = "[" + hostAddr.getHostName() + "]"; - } - return host + ":" + inetAddr.getPort(); - } - return addr.toString(); - } - - private static boolean isIPv6(final InetAddress ip) { - return ip instanceof Inet6Address - && ip.getHostName().equals(ip.getHostAddress()); - } - private final List listen; private final boolean keepAlive; private final List hostKeys; @@ -256,7 +232,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { } try { - r.add(new HostKey(format(inetAddr), keyBin)); + r.add(new HostKey(SocketUtil.format(inetAddr, IANA_SSH_PORT), keyBin)); } catch (JSchException e) { log.warn("Cannot format SSHD host key", e); } @@ -284,7 +260,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { private String addressList() { final StringBuilder r = new StringBuilder(); for (Iterator i = listen.iterator(); i.hasNext();) { - r.append(format(i.next())); + r.append(SocketUtil.format(i.next(), IANA_SSH_PORT)); if (i.hasNext()) { r.append(", "); } @@ -302,7 +278,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { for (final String desc : want) { try { - bind.add(toSocketAddress(desc)); + bind.add(SocketUtil.resolve(desc, DEFAULT_PORT)); } catch (IllegalArgumentException e) { log.error("Bad sshd.listenaddress: " + desc + ": " + e.getMessage()); } @@ -310,57 +286,6 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { return bind; } - private SocketAddress toSocketAddress(final String desc) { - String hostStr; - String portStr; - - if (desc.startsWith("[")) { - // IPv6, as a raw IP address. - // - final int hostEnd = desc.indexOf(']'); - if (hostEnd < 0) { - throw new IllegalArgumentException("invalid IPv6 representation"); - } - - hostStr = desc.substring(1, hostEnd); - portStr = desc.substring(hostEnd + 1); - } else { - // IPv4, or a host name. - // - final int hostEnd = desc.indexOf(':'); - hostStr = 0 <= hostEnd ? desc.substring(0, hostEnd) : desc; - portStr = 0 <= hostEnd ? desc.substring(hostEnd) : ""; - } - - if ("*".equals(hostStr)) { - hostStr = ""; - } - if (portStr.startsWith(":")) { - portStr = portStr.substring(1); - } - - final int port; - if (portStr.length() > 0) { - try { - port = Integer.parseInt(portStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid port"); - } - } else { - port = DEFAULT_PORT; - } - - if (hostStr.length() > 0) { - try { - return new InetSocketAddress(InetAddress.getByName(hostStr), port); - } catch (UnknownHostException e) { - throw new IllegalArgumentException(e.getMessage(), e); - } - } else { - return new InetSocketAddress(port); - } - } - @SuppressWarnings("unchecked") private void initProviderBouncyCastle() { setKeyExchangeFactories(Arrays.> asList(