init: Create a command to setup a new Gerrit installation
The init command uses an interactive prompting process to help the user get a basic website configured. The --import-projects option will automatically search for and import any Git repositories which are discovered within gerrit.basePath. Bug: issue 323 Bug: issue 330 Change-Id: I3d6e8f9f5fea8bfc78f6dfb1fc8f284bebfba670 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -9,8 +9,8 @@ through the Java command line. For example:
|
||||
[[programs]]Programs
|
||||
--------------------
|
||||
|
||||
CreateSchema::
|
||||
Initialize a new database schema.
|
||||
link:pgm-init.html[init]::
|
||||
Initialize a new Gerrit server installation
|
||||
|
||||
link:pgm-daemon.html[daemon]::
|
||||
Gerrit HTTP, SSH network server.
|
||||
@@ -18,6 +18,9 @@ link:pgm-daemon.html[daemon]::
|
||||
version::
|
||||
Display the release version of Gerrit Code Review.
|
||||
|
||||
CreateSchema::
|
||||
Initialize a new database schema.
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
|
||||
51
Documentation/pgm-init.txt
Normal file
51
Documentation/pgm-init.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
init
|
||||
====
|
||||
|
||||
NAME
|
||||
----
|
||||
init - Initialize a new Gerrit server installation
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'java' -jar gerrit.war 'init'
|
||||
-d <SITE_PATH>
|
||||
[\--batch]
|
||||
[\--import-projects]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Creates a new Gerrit server installation, interactively prompting
|
||||
for some basic setup prior to writing default configuration files
|
||||
into a newly created `$site_path`.
|
||||
|
||||
If run an an existing `$site_path`, init will upgrade some resources
|
||||
as necessary. This can be useful to import newly created projects.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
\--batch::
|
||||
Run in batch mode, skipping interactive prompts. Reasonable
|
||||
configuration defaults are chosen based on the whims of
|
||||
the Gerrit developers.
|
||||
|
||||
\--import-projects::
|
||||
Recursively search
|
||||
link:config-gerrit.html#gerrit.basePath[gerrit.basePath]
|
||||
for any Git repositories not yet registered as a project,
|
||||
and initializes a new project for them.
|
||||
|
||||
-d::
|
||||
\--site-path::
|
||||
Location of the gerrit.config file, and all other per-site
|
||||
configuration data, supporting libaries and log files.
|
||||
|
||||
CONTEXT
|
||||
-------
|
||||
This command can only be run on a server which has direct
|
||||
connectivity to the metadata database, and local access to the
|
||||
managed Git repositories.
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
@@ -54,6 +54,7 @@ public final class GerritLauncher {
|
||||
System.err.println("usage: java -jar " + jar + " command [ARG ...]");
|
||||
System.err.println();
|
||||
System.err.println("The most commonly used commands are:");
|
||||
System.err.println(" init Initialize a Gerrit installation");
|
||||
System.err.println(" daemon Run the Gerrit network daemons");
|
||||
System.err.println(" version Display the build version number");
|
||||
System.err.println();
|
||||
|
||||
@@ -72,7 +72,12 @@ public abstract class AbstractProgram {
|
||||
ProxyUtil.configureHttpProxy();
|
||||
return run();
|
||||
} catch (Die err) {
|
||||
System.err.println("fatal: " + err.getMessage());
|
||||
final Throwable cause = err.getCause();
|
||||
final String diemsg = err.getMessage();
|
||||
if (cause != null && !cause.getMessage().equals(diemsg)) {
|
||||
System.err.println("fatal: " + cause.getMessage());
|
||||
}
|
||||
System.err.println("fatal: " + diemsg);
|
||||
return 128;
|
||||
}
|
||||
}
|
||||
|
||||
238
gerrit-pgm/src/main/java/com/google/gerrit/pgm/ConsoleUI.java
Normal file
238
gerrit-pgm/src/main/java/com/google/gerrit/pgm/ConsoleUI.java
Normal file
@@ -0,0 +1,238 @@
|
||||
// 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;
|
||||
|
||||
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
|
||||
|
||||
import java.io.Console;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/** Console based interaction with the invoking user. */
|
||||
public abstract class ConsoleUI {
|
||||
/** Get a UI instance, assuming interactive mode. */
|
||||
public static ConsoleUI getInstance() {
|
||||
return getInstance(false);
|
||||
}
|
||||
|
||||
/** Get a UI instance, possibly forcing batch mode. */
|
||||
public static ConsoleUI getInstance(final boolean batchMode) {
|
||||
Console console = batchMode ? null : System.console();
|
||||
return console != null ? new Interactive(console) : new Batch();
|
||||
}
|
||||
|
||||
/** Constructs an exception indicating the user aborted the operation. */
|
||||
protected static Die abort() {
|
||||
return new Die("aborted by user");
|
||||
}
|
||||
|
||||
/** Obtain all values from an enumeration. */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T extends Enum<?>> T[] all(final T value) {
|
||||
try {
|
||||
return (T[]) value.getClass().getMethod("values").invoke(null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Cannot obtain enumeration values", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new IllegalArgumentException("Cannot obtain enumeration values", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Cannot obtain enumeration values", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalArgumentException("Cannot obtain enumeration values", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException("Cannot obtain enumeration values", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return true if this is a batch UI that has no user interaction. */
|
||||
public abstract boolean isBatch();
|
||||
|
||||
/** Display a header message before a series of prompts. */
|
||||
public abstract void header(String fmt, Object... args);
|
||||
|
||||
/** Request the user to answer a yes/no question. */
|
||||
public abstract boolean yesno(String fmt, Object... args);
|
||||
|
||||
/** Prints a message asking the user to let us know when its safe to continue. */
|
||||
public abstract void waitForUser();
|
||||
|
||||
/** Prompt the user for a string, suggesting a default, and returning choice. */
|
||||
public final String readString(String def, String fmt, Object... args) {
|
||||
if (def != null && def.isEmpty()) {
|
||||
def = null;
|
||||
}
|
||||
return readStringImpl(def, fmt, args);
|
||||
}
|
||||
|
||||
/** Prompt the user for a string, suggesting a default, and returning choice. */
|
||||
protected abstract String readStringImpl(String def, String fmt,
|
||||
Object... args);
|
||||
|
||||
/** Prompt the user for a password, returning the string; null if blank. */
|
||||
public abstract String password(String fmt, Object... args);
|
||||
|
||||
/** Prompt the user to make a choice from an enumeration's values. */
|
||||
public abstract <T extends Enum<?>> T readEnum(T def, String fmt,
|
||||
Object... args);
|
||||
|
||||
|
||||
private static class Interactive extends ConsoleUI {
|
||||
private final Console console;
|
||||
|
||||
Interactive(final Console console) {
|
||||
this.console = console;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatch() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yesno(String fmt, Object... args) {
|
||||
final String prompt = String.format(fmt, args);
|
||||
for (;;) {
|
||||
final String yn = console.readLine("%-30s [y/n]? ", prompt);
|
||||
if (yn == null) {
|
||||
throw abort();
|
||||
}
|
||||
if (yn.equalsIgnoreCase("y") || yn.equalsIgnoreCase("yes")) {
|
||||
return true;
|
||||
}
|
||||
if (yn.equalsIgnoreCase("n") || yn.equalsIgnoreCase("no")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForUser() {
|
||||
if (console.readLine("Press enter to continue ") == null) {
|
||||
throw abort();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String readStringImpl(String def, String fmt, Object... args) {
|
||||
final String prompt = String.format(fmt, args);
|
||||
String r;
|
||||
if (def != null) {
|
||||
r = console.readLine("%-30s [%s]: ", prompt, def);
|
||||
} else {
|
||||
r = console.readLine("%-30s : ", prompt);
|
||||
}
|
||||
if (r == null) {
|
||||
throw abort();
|
||||
}
|
||||
r = r.trim();
|
||||
if (r.isEmpty()) {
|
||||
return def;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String password(String fmt, Object... args) {
|
||||
final String prompt = String.format(fmt, args);
|
||||
for (;;) {
|
||||
final char[] a1 = console.readPassword("%-30s : ", prompt);
|
||||
if (a1 == null) {
|
||||
throw abort();
|
||||
}
|
||||
|
||||
final char[] a2 = console.readPassword("%30s : ", "confirm password");
|
||||
if (a2 == null) {
|
||||
throw abort();
|
||||
}
|
||||
|
||||
final String s1 = new String(a1);
|
||||
final String s2 = new String(a2);
|
||||
if (!s1.equals(s2)) {
|
||||
console.printf("error: Passwords did not match; try again\n");
|
||||
continue;
|
||||
}
|
||||
return !s1.isEmpty() ? s1 : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
|
||||
final String prompt = String.format(fmt, args);
|
||||
final T[] options = all(def);
|
||||
for (;;) {
|
||||
String r = console.readLine("%-30s [%s/?]: ", prompt, def.toString());
|
||||
if (r == null) {
|
||||
throw abort();
|
||||
}
|
||||
r = r.trim();
|
||||
if (r.isEmpty()) {
|
||||
return def;
|
||||
}
|
||||
for (final T e : options) {
|
||||
if (equalsIgnoreCase(e.toString(), r)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
if (!"?".equals(r)) {
|
||||
console.printf("error: '%s' is not a valid choice\n", r);
|
||||
}
|
||||
console.printf(" Supported options are:\n");
|
||||
for (final T e : options) {
|
||||
console.printf(" %s\n", e.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void header(String fmt, Object... args) {
|
||||
fmt = fmt.replaceAll("\n", "\n*** ");
|
||||
console.printf("\n*** " + fmt + "\n*** \n\n", args);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Batch extends ConsoleUI {
|
||||
@Override
|
||||
public boolean isBatch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yesno(String fmt, Object... args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String readStringImpl(String def, String fmt, Object... args) {
|
||||
return def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForUser() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String password(String fmt, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
|
||||
return def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void header(String fmt, Object... args) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package com.google.gerrit.pgm;
|
||||
import com.google.gerrit.lifecycle.LifecycleListener;
|
||||
|
||||
import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.ConsoleAppender;
|
||||
import org.apache.log4j.DailyRollingFileAppender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.LogManager;
|
||||
@@ -29,6 +30,22 @@ import org.apache.log4j.spi.LoggingEvent;
|
||||
import java.io.File;
|
||||
|
||||
public class ErrorLogFile {
|
||||
public static void errorOnlyConsole() {
|
||||
LogManager.resetConfiguration();
|
||||
|
||||
final PatternLayout layout = new PatternLayout();
|
||||
layout.setConversionPattern("%-5p %c %x: %m%n");
|
||||
|
||||
final ConsoleAppender dst = new ConsoleAppender();
|
||||
dst.setLayout(layout);
|
||||
dst.setTarget("System.err");
|
||||
dst.setThreshold(Level.ERROR);
|
||||
|
||||
final Logger root = LogManager.getRootLogger();
|
||||
root.removeAllAppenders();
|
||||
root.addAppender(dst);
|
||||
}
|
||||
|
||||
public static LifecycleListener start(final File sitePath) {
|
||||
final File logdir = new File(sitePath, "logs");
|
||||
if (!logdir.exists() && !logdir.mkdirs()) {
|
||||
|
||||
659
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
Normal file
659
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
Normal file
@@ -0,0 +1,659 @@
|
||||
// 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;
|
||||
|
||||
import static com.google.gerrit.pgm.DataSourceProvider.Type.H2;
|
||||
|
||||
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.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.mail.SmtpEmailSender.Encryption;
|
||||
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.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
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.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
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 {
|
||||
@Option(name = "--batch", usage = "Batch mode; skip interactive prompting")
|
||||
private boolean batchMode;
|
||||
|
||||
@Option(name = "--import-projects", usage = "Import git repositories as projects")
|
||||
private boolean importProjects;
|
||||
|
||||
@Inject
|
||||
private GitRepositoryManager repositoryManager;
|
||||
|
||||
@Inject
|
||||
private SchemaFactory<ReviewDb> schema;
|
||||
|
||||
private boolean deleteOnFailure;
|
||||
private ConsoleUI ui;
|
||||
private Injector dbInjector;
|
||||
private Injector sysInjector;
|
||||
|
||||
@Override
|
||||
public int run() throws Exception {
|
||||
ErrorLogFile.errorOnlyConsole();
|
||||
ui = ConsoleUI.getInstance(batchMode);
|
||||
|
||||
try {
|
||||
initSitePath();
|
||||
inject();
|
||||
initGit();
|
||||
} catch (Exception failure) {
|
||||
if (deleteOnFailure) {
|
||||
recursiveDelete(getSitePath());
|
||||
}
|
||||
throw failure;
|
||||
} catch (Error failure) {
|
||||
if (deleteOnFailure) {
|
||||
recursiveDelete(getSitePath());
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
System.err.println("Initialized " + getSitePath().getCanonicalPath());
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void initSitePath() throws IOException, InterruptedException {
|
||||
final File sitePath = getSitePath();
|
||||
|
||||
final File gerrit_config = new File(sitePath, "gerrit.config");
|
||||
final File secure_config = new File(sitePath, "secure.config");
|
||||
final File replication_config = new File(sitePath, "replication.config");
|
||||
final File lib_dir = new File(sitePath, "lib");
|
||||
final File logs_dir = new File(sitePath, "logs");
|
||||
final File static_dir = new File(sitePath, "static");
|
||||
final File cache_dir = new File(sitePath, "cache");
|
||||
|
||||
if (gerrit_config.exists()) {
|
||||
if (!gerrit_config.exists()) {
|
||||
throw die("'" + sitePath + "' is not a Gerrit server site");
|
||||
}
|
||||
} else if (!gerrit_config.exists()) {
|
||||
ui.header("Gerrit Code Review %s", version());
|
||||
if (!ui.yesno("Initialize '%s'", sitePath.getCanonicalPath())) {
|
||||
throw die("aborted by user");
|
||||
}
|
||||
|
||||
if (!sitePath.mkdirs()) {
|
||||
throw die("Cannot make directory " + sitePath);
|
||||
}
|
||||
deleteOnFailure = true;
|
||||
|
||||
final FileBasedConfig cfg = new FileBasedConfig(gerrit_config);
|
||||
final FileBasedConfig sec = new FileBasedConfig(secure_config);
|
||||
init_gerrit_basepath(cfg);
|
||||
init_database(cfg, sec);
|
||||
init_auth(cfg, sec);
|
||||
init_sendemail(cfg, sec);
|
||||
init_sshd(cfg, sec);
|
||||
init_httpd(cfg, sec);
|
||||
|
||||
cache_dir.mkdir();
|
||||
set(cfg, "cache", "directory", cache_dir.getName());
|
||||
|
||||
cfg.save();
|
||||
saveSecureConfig(sec);
|
||||
|
||||
if (ui != null) {
|
||||
System.err.println();
|
||||
}
|
||||
}
|
||||
|
||||
if (!secure_config.exists()) {
|
||||
chmod600(secure_config);
|
||||
}
|
||||
if (!replication_config.exists()) {
|
||||
replication_config.createNewFile();
|
||||
}
|
||||
|
||||
lib_dir.mkdir();
|
||||
logs_dir.mkdir();
|
||||
static_dir.mkdir();
|
||||
}
|
||||
|
||||
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<String> have = new HashSet<String>();
|
||||
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<String> 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 {
|
||||
chmod600(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 chmod600(final File path) throws IOException {
|
||||
if (!path.exists() && !path.createNewFile()) {
|
||||
throw new IOException("Cannot create " + path);
|
||||
}
|
||||
path.setWritable(false, false /* all */);
|
||||
path.setReadable(false, false /* all */);
|
||||
path.setExecutable(false, false /* all */);
|
||||
|
||||
path.setWritable(true, true /* owner only */);
|
||||
path.setReadable(true, true /* owner only */);
|
||||
if (path.isDirectory()) {
|
||||
path.setExecutable(true, true /* owner only */);
|
||||
}
|
||||
}
|
||||
|
||||
private void init_gerrit_basepath(final Config cfg) {
|
||||
ui.header("Git Repositories");
|
||||
|
||||
File d = new File(ui.readString("git", "Location of Git repositories"));
|
||||
set(cfg, "gerrit", "basePath", d.getPath());
|
||||
|
||||
if (d.exists()) {
|
||||
if (!importProjects && d.list() != null && d.list().length > 0) {
|
||||
importProjects = ui.yesno("Import existing repositories");
|
||||
}
|
||||
} else if (!d.mkdirs()) {
|
||||
throw die("Cannot create " + d);
|
||||
}
|
||||
}
|
||||
|
||||
private void init_database(final Config cfg, final Config sec) {
|
||||
ui.header("SQL Database");
|
||||
|
||||
DataSourceProvider.Type db_type = ui.readEnum(H2, "Database server type");
|
||||
if (db_type == DataSourceProvider.Type.DEFAULT) {
|
||||
db_type = H2;
|
||||
}
|
||||
set(cfg, "database", "type", db_type, null);
|
||||
|
||||
switch (db_type) {
|
||||
case MYSQL:
|
||||
createDownloader()
|
||||
.setRequired(true)
|
||||
.setName("MySQL Connector/J 5.1.10")
|
||||
.setJarUrl(
|
||||
"http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.10/mysql-connector-java-5.1.10.jar")
|
||||
.setSHA1("b83574124f1a00d6f70d56ba64aa52b8e1588e6d").download();
|
||||
loadSiteLib();
|
||||
break;
|
||||
}
|
||||
|
||||
final boolean userPassAuth;
|
||||
switch (db_type) {
|
||||
case H2:
|
||||
userPassAuth = false;
|
||||
new File(getSitePath(), "db").mkdirs();
|
||||
break;
|
||||
|
||||
case JDBC: {
|
||||
userPassAuth = true;
|
||||
String driver = ui.readString("", "Driver class name");
|
||||
String url = ui.readString("", "url");
|
||||
|
||||
set(cfg, "database", "driver", driver);
|
||||
set(cfg, "database", "url", url);
|
||||
break;
|
||||
}
|
||||
|
||||
case POSTGRES:
|
||||
case POSTGRESQL:
|
||||
case MYSQL: {
|
||||
userPassAuth = true;
|
||||
String def_port = "(" + db_type.toString() + " default)";
|
||||
String hostname = ui.readString("localhost", "Server hostname");
|
||||
String port = ui.readString(def_port, "Server port");
|
||||
String database = ui.readString("reviewdb", "Database name");
|
||||
|
||||
set(cfg, "database", "hostname", hostname);
|
||||
set(cfg, "database", "port", port != def_port ? port : null);
|
||||
set(cfg, "database", "database", database);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw die("internal bug, database " + db_type + " not supported");
|
||||
}
|
||||
|
||||
if (userPassAuth) {
|
||||
String user = ui.readString(username(), "Database username");
|
||||
String pass = user != null ? ui.password("%s's password", user) : null;
|
||||
set(cfg, "database", "username", user);
|
||||
set(sec, "database", "password", pass);
|
||||
}
|
||||
}
|
||||
|
||||
private void init_auth(final Config cfg, final Config sec) {
|
||||
ui.header("User Authentication");
|
||||
|
||||
AuthType auth_type = ui.readEnum(AuthType.OPENID, "Authentication method");
|
||||
set(cfg, "auth", "type", auth_type, null);
|
||||
|
||||
switch (auth_type) {
|
||||
case HTTP:
|
||||
case HTTP_LDAP: {
|
||||
String def_hdr = "(HTTP Basic)";
|
||||
String hdr = ui.readString(def_hdr, "Username HTTP header");
|
||||
String logoutUrl = ui.readString("", "Single-sign-on logout URL");
|
||||
|
||||
set(cfg, "auth", "httpHeader", hdr != def_hdr ? hdr : null);
|
||||
set(cfg, "auth", "logoutUrl", logoutUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (auth_type) {
|
||||
case LDAP:
|
||||
case HTTP_LDAP: {
|
||||
String server = ui.readString("ldap://localhost", "LDAP server");
|
||||
|
||||
if (server != null && !server.startsWith("ldap://")
|
||||
&& !server.startsWith("ldaps://")) {
|
||||
if (ui.yesno("Use SSL")) {
|
||||
server = "ldaps://" + server;
|
||||
} else {
|
||||
server = "ldap://" + server;
|
||||
}
|
||||
}
|
||||
|
||||
final String def_dn = dnOf(server);
|
||||
String accountBase = ui.readString(def_dn, "Account BaseDN");
|
||||
String groupBase = ui.readString(accountBase, "Group BaseDN");
|
||||
|
||||
String user = ui.readString(null, "LDAP username");
|
||||
String pass = user != null ? ui.password("%s's password", user) : null;
|
||||
|
||||
set(cfg, "ldap", "server", server);
|
||||
set(cfg, "ldap", "username", user);
|
||||
set(sec, "ldap", "password", pass);
|
||||
|
||||
set(cfg, "ldap", "accountBase", accountBase);
|
||||
set(cfg, "ldap", "groupBase", groupBase);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void init_sendemail(final Config cfg, final Config sec) {
|
||||
ui.header("Email Delivery");
|
||||
|
||||
String def_port = "(default)";
|
||||
String smtpserver = ui.readString("localhost", "SMTP server hostname");
|
||||
String port = ui.readString(def_port, "SMTP server port");
|
||||
Encryption enc = ui.readEnum(Encryption.NONE, "SMTP encryption");
|
||||
String username = null;
|
||||
if (enc != Encryption.NONE || !isLocal(smtpserver)) {
|
||||
username = username();
|
||||
}
|
||||
username = ui.readString(username, "SMTP username");
|
||||
String password =
|
||||
username != null ? ui.password("%s's password", username) : null;
|
||||
|
||||
set(cfg, "sendemail", "smtpServer", smtpserver);
|
||||
set(cfg, "sendemail", "smtpServerPort", port != def_port ? port : null);
|
||||
set(cfg, "sendemail", "smtpEncryption", enc, Encryption.NONE);
|
||||
|
||||
set(cfg, "sendemail", "smtpUser", username);
|
||||
set(sec, "sendemail", "smtpPass", password);
|
||||
}
|
||||
|
||||
private void init_sshd(final Config cfg, final Config sec)
|
||||
throws IOException, InterruptedException {
|
||||
ui.header("SSH Daemon");
|
||||
|
||||
String sshd_hostname = ui.readString("*", "Gerrit SSH listens on address");
|
||||
String sshd_port = ui.readString("29418", "Gerrit SSH listens on port");
|
||||
set(cfg, "sshd", "listenAddress", sshd_hostname + ":" + sshd_port);
|
||||
|
||||
// Download and install BouncyCastle if the user wants to use it.
|
||||
//
|
||||
createDownloader().setRequired(false).setName("Bouncy Castle Crypto v144")
|
||||
.setJarUrl("http://www.bouncycastle.org/download/bcprov-jdk16-144.jar")
|
||||
.setSHA1("6327a5f7a3dc45e0fd735adb5d08c5a74c05c20c").download();
|
||||
loadSiteLib();
|
||||
|
||||
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();
|
||||
final File rsa = new File(getSitePath(), "ssh_host_rsa_key");
|
||||
final File dsa = new File(getSitePath(), "ssh_host_dsa_key");
|
||||
|
||||
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(getSitePath(), "tmp.sshkeygen");
|
||||
if (!tmpdir.mkdir()) {
|
||||
throw die("Cannot create directory " + tmpdir);
|
||||
}
|
||||
chmod600(tmpdir);
|
||||
|
||||
final String keyname = "ssh_host_key";
|
||||
final File tmpkey = new File(tmpdir, keyname);
|
||||
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.
|
||||
chmod600(tmpkey);
|
||||
|
||||
final File key = new File(getSitePath(), keyname);
|
||||
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 init_httpd(final Config cfg, final Config sec)
|
||||
throws IOException, InterruptedException {
|
||||
ui.header("HTTP Daemon");
|
||||
|
||||
final boolean reverseProxy =
|
||||
ui.yesno("Behind reverse HTTP proxy (e.g. Apache mod_proxy)");
|
||||
|
||||
final boolean useSSL;
|
||||
if (reverseProxy) {
|
||||
useSSL = ui.yesno("Does the proxy server use https:// (SSL)");
|
||||
} else {
|
||||
useSSL = ui.yesno("Use https:// (SSL)");
|
||||
}
|
||||
|
||||
final String scheme = useSSL ? "https" : "http";
|
||||
final String port_def = useSSL ? "8443" : "8080";
|
||||
String httpd_hostname = ui.readString(reverseProxy ? "localhost" : "*", //
|
||||
"Gerrit HTTP listens on address");
|
||||
String httpd_port = ui.readString(reverseProxy ? "8081" : port_def, //
|
||||
"Gerrit HTTP listens on port");
|
||||
|
||||
String context = "/";
|
||||
if (reverseProxy) {
|
||||
context = ui.readString("/", "Gerrit's subdirectory on proxy server");
|
||||
if (!context.endsWith("/")) {
|
||||
context += "/";
|
||||
}
|
||||
}
|
||||
|
||||
final String httpd_url = (reverseProxy ? "proxy-" : "") //
|
||||
+ scheme + "://" + httpd_hostname + ":" + httpd_port + context;
|
||||
set(cfg, "httpd", "listenUrl", httpd_url);
|
||||
|
||||
if (useSSL && !reverseProxy
|
||||
&& ui.yesno("Create self-signed SSL certificate")) {
|
||||
final String certName =
|
||||
ui.readString("*".equals(httpd_hostname) ? hostname()
|
||||
: httpd_hostname, "Certificate server name");
|
||||
final String validity =
|
||||
ui.readString("365", "Certificate expires in (days)");
|
||||
|
||||
final String ssl_pass = SignedToken.generateRandomKey();
|
||||
final String dname =
|
||||
"CN=" + certName + ",OU=Gerrit Code Review,O=" + domainOf(certName);
|
||||
|
||||
final File tmpdir = new File(getSitePath(), "tmp.sslcertgen");
|
||||
if (!tmpdir.mkdir()) {
|
||||
throw die("Cannot create directory " + tmpdir);
|
||||
}
|
||||
chmod600(tmpdir);
|
||||
|
||||
final File tmpstore = new File(tmpdir, "keystore");
|
||||
Runtime.getRuntime().exec(new String[] {"keytool", //
|
||||
"-keystore", tmpstore.getAbsolutePath(), //
|
||||
"-storepass", ssl_pass, //
|
||||
"-genkeypair", //
|
||||
"-alias", certName, //
|
||||
"-keyalg", "RSA", //
|
||||
"-validity", validity, //
|
||||
"-dname", dname, //
|
||||
"-keypass", ssl_pass, //
|
||||
}).waitFor();
|
||||
chmod600(tmpstore);
|
||||
|
||||
final File store = new File(getSitePath(), "keystore");
|
||||
if (!tmpstore.renameTo(store)) {
|
||||
throw die("Cannot rename " + tmpstore + " to " + store);
|
||||
}
|
||||
if (!tmpdir.delete()) {
|
||||
throw die("Cannot delete " + tmpdir);
|
||||
}
|
||||
|
||||
set(sec, "httpd", "sslKeyPassword", ssl_pass);
|
||||
set(cfg, "gerrit", "canonicalWebUrl", "https://" + certName + ":"
|
||||
+ httpd_port + context);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Enum<?>> void set(Config cfg, String section, String name,
|
||||
T value, T def) {
|
||||
if (value != null && value != def) {
|
||||
cfg.setString(section, null, name, value.toString());
|
||||
} else {
|
||||
cfg.unset(section, null, name);
|
||||
}
|
||||
}
|
||||
|
||||
private void set(Config cfg, String section, String name, String value) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
cfg.setString(section, null, name, value);
|
||||
} else {
|
||||
cfg.unset(section, null, name);
|
||||
}
|
||||
}
|
||||
|
||||
private void inject() {
|
||||
dbInjector = createDbInjector();
|
||||
sysInjector = createSysInjector();
|
||||
sysInjector.injectMembers(this);
|
||||
}
|
||||
|
||||
private Injector createSysInjector() {
|
||||
final List<Module> modules = new ArrayList<Module>();
|
||||
modules.add(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(GitRepositoryManager.class);
|
||||
}
|
||||
});
|
||||
return dbInjector.createChildInjector(modules);
|
||||
}
|
||||
|
||||
private LibraryDownloader createDownloader() {
|
||||
return new LibraryDownloader(ui, getSitePath());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void recursiveDelete(File path) {
|
||||
File[] entries = path.listFiles();
|
||||
if (entries != null) {
|
||||
for (File e : entries) {
|
||||
recursiveDelete(e);
|
||||
}
|
||||
}
|
||||
if (!path.delete() && path.exists()) {
|
||||
System.err.println("warn: Cannot remove " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.util.HttpSupport;
|
||||
|
||||
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.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/** Get optional or required 3rd party library files into $site_path/lib. */
|
||||
class LibraryDownloader {
|
||||
private final ConsoleUI console;
|
||||
private final File libDirectory;
|
||||
private boolean required;
|
||||
private String name;
|
||||
private String jarUrl;
|
||||
private String sha1;
|
||||
private File dst;
|
||||
|
||||
LibraryDownloader(final ConsoleUI console, final File sitePath) {
|
||||
this.console = console;
|
||||
this.libDirectory = new File(sitePath, "lib");
|
||||
}
|
||||
|
||||
LibraryDownloader setRequired(final boolean required) {
|
||||
this.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
LibraryDownloader setName(final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
LibraryDownloader setJarUrl(final String url) {
|
||||
this.jarUrl = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
LibraryDownloader setSHA1(final String sha1) {
|
||||
this.sha1 = sha1;
|
||||
return this;
|
||||
}
|
||||
|
||||
void download() {
|
||||
if (jarUrl == null || !jarUrl.contains("/")) {
|
||||
throw new IllegalStateException("Invalid JarUrl for " + name);
|
||||
}
|
||||
|
||||
final String jarName = jarUrl.substring(jarUrl.lastIndexOf('/') + 1);
|
||||
if (jarName.contains("/") || jarName.contains("\\")) {
|
||||
throw new IllegalStateException("Invalid JarUrl: " + jarUrl);
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
name = jarName;
|
||||
}
|
||||
|
||||
dst = new File(libDirectory, jarName);
|
||||
if (!dst.exists() && shouldGet()) {
|
||||
doGet();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldGet() {
|
||||
if (console.isBatch()) {
|
||||
return required;
|
||||
|
||||
} else {
|
||||
final StringBuilder msg = new StringBuilder();
|
||||
msg.append("\n");
|
||||
msg.append("Gerrit Code Review is not shipped with %s\n");
|
||||
if (required) {
|
||||
msg.append("** This library is required for your configuration. **\n");
|
||||
} else {
|
||||
msg.append(" If available, Gerrit can take advantage of features\n");
|
||||
msg.append(" in the library, but will also function without it.\n");
|
||||
}
|
||||
msg.append("Download and install it now");
|
||||
return console.yesno(msg.toString(), name);
|
||||
}
|
||||
}
|
||||
|
||||
private void doGet() {
|
||||
if (!libDirectory.exists() && !libDirectory.mkdirs()) {
|
||||
throw new Die("Cannot create " + libDirectory);
|
||||
}
|
||||
|
||||
try {
|
||||
doGetByHttp();
|
||||
verifyFileChecksum();
|
||||
} catch (IOException err) {
|
||||
dst.delete();
|
||||
|
||||
if (console.isBatch()) {
|
||||
throw new Die("error: Cannot get " + jarUrl, err);
|
||||
}
|
||||
|
||||
System.err.println();
|
||||
System.err.println();
|
||||
System.err.println("error: " + err.getMessage());
|
||||
System.err.println("Please download:");
|
||||
System.err.println();
|
||||
System.err.println(" " + jarUrl);
|
||||
System.err.println();
|
||||
System.err.println("and save as:");
|
||||
System.err.println();
|
||||
System.err.println(" " + dst.getAbsolutePath());
|
||||
System.err.println();
|
||||
System.err.flush();
|
||||
|
||||
console.waitForUser();
|
||||
|
||||
if (dst.exists()) {
|
||||
verifyFileChecksum();
|
||||
|
||||
} else if (!console.yesno("Continue without this library")) {
|
||||
throw new Die("aborted by user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doGetByHttp() throws IOException {
|
||||
System.err.print("Downloading " + jarUrl + " ...");
|
||||
System.err.flush();
|
||||
try {
|
||||
final ProxySelector proxySelector = ProxySelector.getDefault();
|
||||
final URL url = new URL(jarUrl);
|
||||
final Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
|
||||
final HttpURLConnection c = (HttpURLConnection) url.openConnection(proxy);
|
||||
final InputStream in;
|
||||
|
||||
switch (HttpSupport.response(c)) {
|
||||
case HttpURLConnection.HTTP_OK:
|
||||
in = c.getInputStream();
|
||||
break;
|
||||
|
||||
case HttpURLConnection.HTTP_NOT_FOUND:
|
||||
throw new FileNotFoundException(url.toString());
|
||||
|
||||
default:
|
||||
throw new IOException(url.toString() + ": " + HttpSupport.response(c)
|
||||
+ " " + c.getResponseMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
final OutputStream out = new FileOutputStream(dst);
|
||||
try {
|
||||
final byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
System.err.println(" OK");
|
||||
System.err.flush();
|
||||
} catch (IOException err) {
|
||||
dst.delete();
|
||||
System.err.println(" !! FAIL !!");
|
||||
System.err.flush();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyFileChecksum() {
|
||||
if (sha1 != null) {
|
||||
try {
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
final FileInputStream in = new FileInputStream(dst);
|
||||
try {
|
||||
final byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = in.read(buf)) > 0) {
|
||||
md.update(buf, 0, n);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
|
||||
if (sha1.equals(ObjectId.fromRaw(md.digest()).name())) {
|
||||
System.err.println("Checksum " + dst.getName() + " OK");
|
||||
System.err.flush();
|
||||
|
||||
} else if (console.isBatch()) {
|
||||
dst.delete();
|
||||
throw new Die(dst + " SHA-1 checksum does not match");
|
||||
|
||||
} else if (!console.yesno("error: SHA-1 checksum does not match\n"
|
||||
+ "Use %s anyway", dst.getName())) {
|
||||
dst.delete();
|
||||
throw new Die("aborted by user");
|
||||
}
|
||||
|
||||
} catch (IOException checksumError) {
|
||||
dst.delete();
|
||||
throw new Die("cannot checksum " + dst, checksumError);
|
||||
|
||||
} catch (NoSuchAlgorithmException checksumError) {
|
||||
dst.delete();
|
||||
throw new Die("cannot checksum " + dst, checksumError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,19 +31,22 @@ import com.google.inject.name.Names;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public abstract class SiteProgram extends AbstractProgram {
|
||||
private boolean siteLibLoaded;
|
||||
|
||||
@Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data")
|
||||
private File sitePath = new File(".");
|
||||
|
||||
@@ -58,30 +61,38 @@ public abstract class SiteProgram extends AbstractProgram {
|
||||
|
||||
/** Load extra JARs from {@code lib/} subdirectory of {@link #getSitePath()} */
|
||||
protected void loadSiteLib() {
|
||||
if (!siteLibLoaded) {
|
||||
final File libdir = new File(getSitePath(), "lib");
|
||||
final File[] list = libdir.listFiles();
|
||||
if (list != null) {
|
||||
final List<File> toLoad = new ArrayList<File>();
|
||||
for (final File u : list) {
|
||||
if (u.isFile() && (u.getName().endsWith(".jar") //
|
||||
|| u.getName().endsWith(".zip"))) {
|
||||
toLoad.add(u);
|
||||
final File[] list = libdir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File path) {
|
||||
if (!path.isFile()) {
|
||||
return false;
|
||||
}
|
||||
return path.getName().endsWith(".jar") //
|
||||
|| path.getName().endsWith(".zip");
|
||||
}
|
||||
addToClassLoader(toLoad);
|
||||
});
|
||||
if (list != null && 0 < list.length) {
|
||||
Arrays.sort(list, new Comparator<File>() {
|
||||
@Override
|
||||
public int compare(File a, File b) {
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
|
||||
siteLibLoaded = true;
|
||||
});
|
||||
addToClassLoader(list);
|
||||
}
|
||||
}
|
||||
|
||||
private void addToClassLoader(final List<File> additionalLocations) {
|
||||
private void addToClassLoader(final File[] additionalLocations) {
|
||||
final ClassLoader cl = getClass().getClassLoader();
|
||||
if (!(cl instanceof URLClassLoader)) {
|
||||
throw noAddURL("Not loaded by URLClassLoader", null);
|
||||
}
|
||||
|
||||
final URLClassLoader ucl = (URLClassLoader) cl;
|
||||
final Set<URL> have = new HashSet<URL>();
|
||||
have.addAll(Arrays.asList(ucl.getURLs()));
|
||||
|
||||
final Method m;
|
||||
try {
|
||||
m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
|
||||
@@ -92,17 +103,20 @@ public abstract class SiteProgram extends AbstractProgram {
|
||||
throw noAddURL("Method addURL not available", e);
|
||||
}
|
||||
|
||||
for (final File u : additionalLocations) {
|
||||
for (final File path : additionalLocations) {
|
||||
try {
|
||||
m.invoke(cl, u.toURI().toURL());
|
||||
final URL url = path.toURI().toURL();
|
||||
if (have.add(url)) {
|
||||
m.invoke(cl, url);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw noAddURL("addURL " + u + " failed", e);
|
||||
throw noAddURL("addURL " + path + " failed", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw noAddURL("addURL " + u + " failed", e);
|
||||
throw noAddURL("addURL " + path + " failed", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw noAddURL("addURL " + u + " failed", e);
|
||||
throw noAddURL("addURL " + path + " failed", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw noAddURL("addURL " + u + " failed", e.getCause());
|
||||
throw noAddURL("addURL " + path + " failed", e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,13 @@ import org.eclipse.jgit.lib.RepositoryCache;
|
||||
import org.eclipse.jgit.lib.WindowCache;
|
||||
import org.eclipse.jgit.lib.WindowCacheConfig;
|
||||
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
|
||||
import org.eclipse.jgit.util.IO;
|
||||
import org.eclipse.jgit.util.RawParseUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Class managing Git repositories. */
|
||||
@@ -40,6 +43,9 @@ import java.io.IOException;
|
||||
public class GitRepositoryManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(GitRepositoryManager.class);
|
||||
|
||||
private static final String UNNAMED =
|
||||
"Unnamed repository; edit this file to name it for gitweb.";
|
||||
|
||||
public static class Lifecycle implements LifecycleListener {
|
||||
private final Config cfg;
|
||||
|
||||
@@ -147,10 +153,46 @@ public class GitRepositoryManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the {@code GIT_DIR/description} file for gitweb.
|
||||
* <p>
|
||||
* NB: This code should really be in JGit, as a member of the Repository
|
||||
* object. Until it moves there, its here.
|
||||
*
|
||||
* @param name the repository name, relative to the base directory.
|
||||
* @return description text; null if no description has been configured.
|
||||
* @throws RepositoryNotFoundException the named repository does not exist.
|
||||
* @throws IOException the description file exists, but is not readable by
|
||||
* this process.
|
||||
*/
|
||||
public String getProjectDescription(final String name)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
final Repository e = openRepository(name);
|
||||
final File d = new File(e.getDirectory(), "description");
|
||||
|
||||
String description;
|
||||
try {
|
||||
description = RawParseUtils.decode(IO.readFully(d));
|
||||
} catch (FileNotFoundException err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (description != null) {
|
||||
description = description.trim();
|
||||
if (description.isEmpty()) {
|
||||
description = null;
|
||||
}
|
||||
if (UNNAMED.equals(description)) {
|
||||
description = null;
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code GIT_DIR/description} file for gitweb.
|
||||
* <p>
|
||||
* NB: This code should really be in JGit, as a member of the Repostiory
|
||||
* NB: This code should really be in JGit, as a member of the Repository
|
||||
* object. Until it moves there, its here.
|
||||
*
|
||||
* @param name the repository name, relative to the base directory.
|
||||
|
||||
Reference in New Issue
Block a user