001/* 002 * (C) Copyright 2010-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Julien Carsique 018 * Florent Guillaume 019 * Ronan DANIELLOU 020 */ 021package org.nuxeo.launcher; 022 023import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_COMPANY; 024import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_COMPANY_REGEX; 025import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_DESCRIPTION; 026import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_EMAIL; 027import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_PASSWORD; 028import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_PASSWORDND; 029import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_PROJECT; 030import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_PROJECT_REGEX; 031import static org.nuxeo.launcher.connect.ConnectRegistrationBroker.REGISTRATION_TERMSNCONDITIONS; 032 033import java.io.Console; 034import java.io.File; 035import java.io.FileWriter; 036import java.io.FilenameFilter; 037import java.io.IOException; 038import java.io.OutputStream; 039import java.io.OutputStreamWriter; 040import java.net.SocketTimeoutException; 041import java.nio.file.Files; 042import java.nio.file.Path; 043import java.nio.file.Paths; 044import java.nio.file.StandardOpenOption; 045import java.security.GeneralSecurityException; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collection; 049import java.util.Date; 050import java.util.HashMap; 051import java.util.Iterator; 052import java.util.List; 053import java.util.Map; 054import java.util.concurrent.ExecutionException; 055import java.util.concurrent.ExecutorService; 056import java.util.concurrent.Executors; 057import java.util.function.Function; 058import java.util.function.Predicate; 059import java.util.regex.Pattern; 060import java.util.stream.Collectors; 061import java.util.zip.GZIPOutputStream; 062 063import javax.json.Json; 064import javax.json.stream.JsonGenerator; 065import javax.validation.constraints.NotNull; 066import javax.xml.bind.JAXBContext; 067import javax.xml.bind.JAXBException; 068import javax.xml.bind.Marshaller; 069import javax.xml.stream.FactoryConfigurationError; 070import javax.xml.stream.XMLOutputFactory; 071import javax.xml.stream.XMLStreamException; 072import javax.xml.stream.XMLStreamWriter; 073 074import org.apache.commons.cli.CommandLine; 075import org.apache.commons.cli.CommandLineParser; 076import org.apache.commons.cli.DefaultParser; 077import org.apache.commons.cli.HelpFormatter; 078import org.apache.commons.cli.Option; 079import org.apache.commons.cli.OptionGroup; 080import org.apache.commons.cli.Options; 081import org.apache.commons.cli.ParseException; 082import org.apache.commons.codec.binary.Base64; 083import org.apache.commons.io.IOUtils; 084import org.apache.commons.lang.ArrayUtils; 085import org.apache.commons.lang.StringUtils; 086import org.apache.commons.lang.SystemUtils; 087import org.apache.commons.lang.text.StrSubstitutor; 088import org.apache.commons.logging.Log; 089import org.apache.commons.logging.LogFactory; 090import org.apache.commons.logging.impl.SimpleLog; 091import org.apache.commons.validator.routines.EmailValidator; 092import org.nuxeo.common.Environment; 093import org.nuxeo.common.codec.Crypto; 094import org.nuxeo.common.codec.CryptoProperties; 095import org.nuxeo.connect.connector.NuxeoClientInstanceType; 096import org.nuxeo.connect.data.ConnectProject; 097import org.nuxeo.connect.identity.LogicalInstanceIdentifier.NoCLID; 098import org.nuxeo.connect.registration.RegistrationException; 099import org.nuxeo.connect.tools.report.client.ReportConnector; 100import org.nuxeo.connect.update.PackageException; 101import org.nuxeo.connect.update.Version; 102import org.nuxeo.launcher.config.ConfigurationException; 103import org.nuxeo.launcher.config.ConfigurationGenerator; 104import org.nuxeo.launcher.connect.ConnectBroker; 105import org.nuxeo.launcher.connect.ConnectRegistrationBroker; 106import org.nuxeo.launcher.daemon.DaemonThreadFactory; 107import org.nuxeo.launcher.gui.NuxeoLauncherGUI; 108import org.nuxeo.launcher.info.CommandInfo; 109import org.nuxeo.launcher.info.CommandSetInfo; 110import org.nuxeo.launcher.info.ConfigurationInfo; 111import org.nuxeo.launcher.info.DistributionInfo; 112import org.nuxeo.launcher.info.InstanceInfo; 113import org.nuxeo.launcher.info.KeyValueInfo; 114import org.nuxeo.launcher.info.MessageInfo; 115import org.nuxeo.launcher.info.PackageInfo; 116import org.nuxeo.launcher.monitoring.StatusServletClient; 117import org.nuxeo.launcher.process.MacProcessManager; 118import org.nuxeo.launcher.process.ProcessManager; 119import org.nuxeo.launcher.process.PureJavaProcessManager; 120import org.nuxeo.launcher.process.SolarisProcessManager; 121import org.nuxeo.launcher.process.UnixProcessManager; 122import org.nuxeo.launcher.process.WindowsProcessManager; 123import org.nuxeo.log4j.Log4JHelper; 124import org.nuxeo.log4j.ThreadedStreamGobbler; 125 126import com.sun.jersey.api.json.JSONConfiguration; 127import com.sun.jersey.json.impl.writer.JsonXmlStreamWriter; 128 129/** 130 * @author jcarsique 131 * @since 5.4.2 132 */ 133public abstract class NuxeoLauncher { 134 135 /** 136 * @since 7.4 137 */ 138 protected static final String OUTPUT_UNSET_VALUE = "<unset>"; 139 140 /** 141 * @since 5.6 142 */ 143 protected static final String OPTION_NODEPS = "nodeps"; 144 145 private static final String OPTION_NODEPS_DESC = "Ignore package dependencies and constraints."; 146 147 /** 148 * @since 5.6 149 */ 150 protected static final String OPTION_GUI = "gui"; 151 152 private static final String OPTION_GUI_DESC = "Start graphical user interface (default is true on Windows and false on other platforms)."; 153 154 /** 155 * @since 5.6 156 */ 157 protected static final String OPTION_JSON = "json"; 158 159 private static final String OPTION_JSON_DESC = "Output JSON for mp-* commands."; 160 161 /** 162 * @since 5.6 163 */ 164 protected static final String OPTION_XML = "xml"; 165 166 private static final String OPTION_XML_DESC = "Output XML for mp-* commands."; 167 168 /** 169 * @since 5.6 170 */ 171 protected static final String OPTION_DEBUG = "debug"; 172 173 private static final String OPTION_DEBUG_DESC = "Activate debug messages.\n" 174 + "<categories>: comma-separated Java categories to debug (default: \"org.nuxeo.launcher\")."; 175 176 /** 177 * @since 7.4 178 */ 179 private static final String OPTION_DEBUG_CATEGORY_ARG_NAME = "categories"; 180 181 /** 182 * @since 5.6 183 */ 184 protected static final String OPTION_DEBUG_CATEGORY = "dc"; 185 186 private static final String OPTION_DEBUG_CATEGORY_DESC = "Deprecated: see categories on '--debug' option."; 187 188 /** 189 * @since 5.6 190 */ 191 protected static final String OPTION_QUIET = "quiet"; 192 193 private static final String OPTION_QUIET_DESC = "Suppress information messages."; 194 195 /** 196 * @since 5.6 197 */ 198 protected static final String OPTION_HELP = "help"; 199 200 private static final String OPTION_HELP_DESC = "Show detailed help."; 201 202 /** 203 * @since 5.6 204 */ 205 protected static final String OPTION_RELAX = "relax"; 206 207 private static final String OPTION_RELAX_DESC = "Allow relax constraint on current platform (default: " 208 + ConnectBroker.OPTION_RELAX_DEFAULT + ")."; 209 210 /** 211 * @since 5.6 212 */ 213 protected static final String OPTION_ACCEPT = "accept"; 214 215 private static final String OPTION_ACCEPT_DESC = "Accept, refuse or ask confirmation for all changes (default: " 216 + ConnectBroker.OPTION_ACCEPT_DEFAULT + ").\n" 217 + "In non interactive mode, '--accept=true' also sets '--relax=true' if needed."; 218 219 /** 220 * @since 5.9.1 221 */ 222 protected static final String OPTION_SNAPSHOT = "snapshot"; 223 224 private static final String OPTION_SNAPSHOT_DESC = "Allow use of SNAPSHOT Nuxeo Packages.\n" 225 + "This option is implicit:\n" // 226 + "\t- on SNAPSHOT distributions (daily builds),\n" 227 + "\t- if the command explicitly requests a SNAPSHOT package."; 228 229 /** 230 * @since 5.9.1 231 */ 232 protected static final String OPTION_FORCE = "force"; 233 234 private static final String OPTION_FORCE_DESC = "Deprecated: use '--strict' option instead."; 235 236 /** 237 * @since 7.4 238 */ 239 protected static final String OPTION_STRICT = "strict"; 240 241 private static final String OPTION_STRICT_DESC = "Abort in error the start command when a component cannot " 242 + "be activated or if a server is already running."; 243 244 /** 245 * @since 5.6 246 */ 247 protected static final String OPTION_HIDE_DEPRECATION = "hide-deprecation-warnings"; 248 249 protected static final String OPTION_HIDE_DEPRECATION_DESC = "Hide deprecation warnings."; 250 251 /** 252 * @since 6.0 253 */ 254 protected static final String OPTION_IGNORE_MISSING = "ignore-missing"; 255 256 protected static final String OPTION_IGNORE_MISSING_DESC = "Ignore unknown packages on mp-add, mp-install and mp-set commands."; 257 258 /** 259 * @since 6.0 260 */ 261 protected static final String OPTION_CLID = "clid"; 262 263 private static final String OPTION_CLID_DESC = "Use the provided instance CLID file"; 264 265 /** 266 * @since 7.4 267 */ 268 protected static final String OPTION_ENCRYPT = "encrypt"; 269 270 private static final String OPTION_ENCRYPT_ARG_NAME = "algorithm"; 271 272 private static final String OPTION_ENCRYPT_DESC = String.format( 273 "Activate key value symmetric encryption.\n" 274 + "The algorithm can be configured: <%s> is a cipher transformation of the form: \"algorithm/mode/padding\" or \"algorithm\".\n" 275 + "Default value is \"%s\" (Advanced Encryption Standard, Electronic Cookbook Mode, PKCS5-style padding).", 276 OPTION_ENCRYPT, Crypto.DEFAULT_ALGO); 277 278 /** 279 * @since 7.4 280 */ 281 protected static final String OPTION_SET = "set"; 282 283 private static final String OPTION_SET_ARG_NAME = "template"; 284 285 private static final String OPTION_SET_DESC = String.format( 286 "Set the value for a given key.\n" 287 + "The value is stored in {{%s}} by default unless a template name is provided; if so, it is then stored in the template's {{%s}} file.\n" 288 + "If the value is empty (''), then the property is unset.\n" 289 + "This option is implicit if no '--get' or '--get-regexp' option is used and there are exactly two parameters (key value).", 290 ConfigurationGenerator.NUXEO_CONF, ConfigurationGenerator.NUXEO_DEFAULT_CONF); 291 292 /** 293 * @since 7.4 294 */ 295 protected static final String OPTION_GET = "get"; 296 297 private static final String OPTION_GET_DESC = "Get the value for a given key. Returns error code 6 if the key was not found.\n" 298 + "This option is implicit if '--set' option is not used and there are more or less than two parameters."; 299 300 /** 301 * @since 7.4 302 */ 303 protected static final String OPTION_GET_REGEXP = "get-regexp"; 304 305 private static final String OPTION_GET_REGEXP_DESC = "Get the value for all keys matching the given regular expression(s)."; 306 307 /** 308 * @since 8.3 309 */ 310 protected static final String OPTION_GZIP_OUTPUT = "gzip"; 311 312 private static final String OPTION_GZIP_DESC = "Compress the output."; 313 314 /** 315 * @since 8.3 316 */ 317 protected static final String OPTION_OUTPUT = "output"; 318 319 private static final String OPTION_OUTPUT_DESC = "Write output in specified file."; 320 321 /** 322 * @since 8.3 323 */ 324 protected static final String OPTION_PRETTY_PRINT = "pretty-print"; 325 326 private static final String OPTION_PRETTY_PRINT_DESC = "Pretty print the output."; 327 328 // Fallback to avoid an error when the log dir is not initialized 329 static { 330 if (System.getProperty(Environment.NUXEO_LOG_DIR) == null) { 331 System.setProperty(Environment.NUXEO_LOG_DIR, "."); 332 } 333 } 334 335 /** 336 * @since 5.6 337 */ 338 private static final String DEFAULT_NUXEO_CONTEXT_PATH = "/nuxeo"; 339 340 static final Log log = LogFactory.getLog(NuxeoLauncher.class); 341 342 private static Options options = initParserOptions();; 343 344 private static final String JAVA_OPTS_PROPERTY = "launcher.java.opts"; 345 346 private static final String JAVA_OPTS_DEFAULT = "-Xms512m -Xmx1024m"; 347 348 private static final String OVERRIDE_JAVA_TMPDIR_PARAM = "launcher.override.java.tmpdir"; 349 350 protected boolean overrideJavaTmpDir; 351 352 private static final String START_MAX_WAIT_PARAM = "launcher.start.max.wait"; 353 354 private static final String STOP_MAX_WAIT_PARAM = "launcher.stop.max.wait"; 355 356 /** 357 * Default maximum time to wait for server startup summary in logs (in 358 * seconds). 359 */ 360 private static final String START_MAX_WAIT_DEFAULT = "300"; 361 362 /** 363 * Default maximum time to wait for effective stop (in seconds) 364 */ 365 private static final String STOP_MAX_WAIT_DEFAULT = "60"; 366 367 /** 368 * Number of try to cleanly stop server before killing process 369 */ 370 private static final int STOP_NB_TRY = 5; 371 372 private static final int STOP_SECONDS_BEFORE_NEXT_TRY = 2; 373 374 private static final long STREAM_MAX_WAIT = 3000; 375 376 private static final String PACK_TOMCAT_CLASS = "org.nuxeo.runtime.deployment.preprocessor.PackWar"; 377 378 private static final String PARAM_UPDATECENTER_DISABLED = "nuxeo.updatecenter.disabled"; 379 380 private static final String[] COMMANDS_NO_GUI = { "configure", "mp-init", "mp-purge", "mp-add", "mp-install", 381 "mp-uninstall", "mp-request", "mp-remove", "mp-hotfix", "mp-upgrade", "mp-reset", "mp-list", "mp-listall", 382 "mp-update", "status", "showconf", "mp-show", "mp-set", "config", "encrypt", "decrypt", OPTION_HELP, 383 "register", "register-trial", "connect-report" }; 384 385 private static final String[] COMMANDS_NO_RUNNING_SERVER = { "pack", "mp-init", "mp-purge", "mp-add", "mp-install", 386 "mp-uninstall", "mp-request", "mp-remove", "mp-hotfix", "mp-upgrade", "mp-reset", "mp-update", "mp-set" }; 387 388 /** 389 * @since 7.4 390 */ 391 protected boolean commandRequiresNoRunningServer() { 392 return Arrays.asList(COMMANDS_NO_RUNNING_SERVER).contains(command); 393 } 394 395 /** 396 * @since 7.4 397 */ 398 protected boolean commandRequiresNoGUI() { 399 return Arrays.asList(COMMANDS_NO_GUI).contains(command); 400 } 401 402 /** 403 * Program is running or service is OK. 404 * 405 * @since 5.7 406 */ 407 public static final int STATUS_CODE_ON = 0; 408 409 /** 410 * Program is not running. 411 * 412 * @since 5.7 413 */ 414 public static final int STATUS_CODE_OFF = 3; 415 416 /** 417 * Program or service status is unknown. 418 * 419 * @since 5.7 420 */ 421 public static final int STATUS_CODE_UNKNOWN = 4; 422 423 /** 424 * @since 5.7 425 */ 426 public static final int EXIT_CODE_OK = 0; 427 428 /** 429 * Generic or unspecified error. 430 * 431 * @since 5.7 432 */ 433 public static final int EXIT_CODE_ERROR = 1; 434 435 /** 436 * Invalid or excess argument(s). 437 * 438 * @since 5.7 439 */ 440 public static final int EXIT_CODE_INVALID = 2; 441 442 /** 443 * Unimplemented feature. 444 * 445 * @since 5.7 446 */ 447 public static final int EXIT_CODE_UNIMPLEMENTED = 3; 448 449 /** 450 * User had insufficient privilege. 451 * 452 * @since 5.7 453 */ 454 public static final int EXIT_CODE_UNAUTHORIZED = 4; 455 456 /** 457 * Program is not installed. 458 * 459 * @since 5.7 460 */ 461 public static final int EXIT_CODE_NOT_INSTALLED = 5; 462 463 /** 464 * Program is not configured. 465 * 466 * @since 5.7 467 */ 468 public static final int EXIT_CODE_NOT_CONFIGURED = 6; 469 470 /** 471 * Program is not running. 472 * 473 * @since 5.7 474 */ 475 public static final int EXIT_CODE_NOT_RUNNING = 7; 476 477 private static final String OPTION_HELP_DESC_ENV = "\nENVIRONMENT VARIABLES\n" 478 + " NUXEO_HOME\t\tPath to server root directory.\n" // 479 + " NUXEO_CONF\t\tPath to {{nuxeo.conf}} file.\n" 480 + " PATH\n" 481 + "\tJAVA\t\t\tPath to the {{java}} executable.\n" 482 + " JAVA_HOME\t\tPath to the Java home directory. Can also be defined in {{nuxeo.conf}}.\n" 483 + " JAVA_OPTS\t\tOptional values passed to the JVM. Can also be defined in {{nuxeo.conf}}.\n" 484 + " REQUIRED_JAVA_VERSION\tNuxeo requirement on Java version.\n" // 485 + "\nJAVA USAGE\n" 486 + String.format(" java [-D%s=\"JVM options\"]" 487 + " [-D%s=\"/path/to/nuxeo\"] [-D%s=\"/path/to/nuxeo.conf\"]" 488 + " [-Djvmcheck=nofail] -jar \"path/to/nuxeo-launcher.jar\" \\\n" 489 + " \t[options] <command> [command parameters]\n\n", JAVA_OPTS_PROPERTY, 490 Environment.NUXEO_HOME, ConfigurationGenerator.NUXEO_CONF) 491 + String.format(" %s\tParameters for the server JVM (default are %s).\n", JAVA_OPTS_PROPERTY, 492 JAVA_OPTS_DEFAULT) 493 + String.format(" %s\t\tNuxeo server root path (default is parent of called script).\n", 494 Environment.NUXEO_HOME) 495 + String.format(" %s\t\tPath to {{%1$s}} file (default is \"$NUXEO_HOME/bin/%1$s\").\n", 496 ConfigurationGenerator.NUXEO_CONF) 497 + " jvmcheck\t\tIf set to \"nofail\", ignore JVM version validation errors.\n"; 498 499 private static final String OPTION_HELP_DESC_COMMANDS = "\nCOMMANDS\n" 500 + " help\t\t\tPrint this message.\n" 501 + " gui\t\t\tDeprecated: use '--gui' option instead.\n" 502 + " start\t\t\tStart Nuxeo server in background, waiting for effective start. Useful for batch executions requiring the server being immediately available after the script returned.\n" 503 + " stop\t\t\tStop any Nuxeo server started with the same {{nuxeo.conf}} file.\n" 504 + " restart\t\t\tRestart Nuxeo server.\n" 505 + " config\t\t\tGet and set template or global parameters.\n" 506 + " encrypt\t\t\tOutput encrypted value for a given parameter.\n" 507 + " decrypt\t\t\tOutput decrypted value for a given parameter.\n" 508 + " configure\t\tConfigure Nuxeo server with parameters from {{nuxeo.conf}}.\n" 509 + " wizard\t\t\tStart the wizard.\n" 510 + " console\t\t\tStart Nuxeo server in a console mode. Ctrl-C will stop it.\n" 511 + " status\t\t\tPrint server running status.\n" 512 + " startbg\t\t\tStart Nuxeo server in background, without waiting for effective start. Useful for starting Nuxeo as a service.\n" 513 + " restartbg\t\tRestart Nuxeo server with a call to \"startbg\" after \"stop\".\n" 514 + " pack\t\t\tBuild a static archive.\n" 515 + " showconf\t\tDisplay the instance configuration.\n" 516 + " connect-report\t\tDump a json report about the running server.\n" 517 + " mp-list\t\t\tList local Nuxeo Packages.\n" 518 + " mp-listall\t\tList all Nuxeo Packages.\n" 519 + " mp-init\t\t\tPre-cache Nuxeo Packages locally available in the distribution.\n" 520 + " mp-update\t\tUpdate cache of Nuxeo Packages list.\n" 521 + " mp-add\t\t\tAdd Nuxeo Package(s) to local cache. You must provide the package file(s), name(s) or ID(s) as parameter.\n" 522 + " mp-install\t\tRun Nuxeo Package installation. It is automatically called at startup if {{installAfterRestart.log}} file exists in data directory. Else you must provide the package file(s), name(s) or ID(s) as parameter.\n" 523 + " mp-uninstall\t\tUninstall Nuxeo Package(s). You must provide the package name(s) or ID(s) as parameter (see \"mp-list\" command).\n" 524 + " mp-remove\t\tRemove Nuxeo Package(s) from the local cache. You must provide the package name(s) or ID(s) as parameter (see \"mp-list\" command).\n" 525 + " mp-reset\t\tReset all packages to DOWNLOADED state. May be useful after a manual server upgrade.\n" 526 + " mp-set\t\t\tInstall a list of Nuxeo Packages and remove those not in the list.\n" 527 + " mp-request\t\tInstall and uninstall Nuxeo Package(s) in one command. You must provide a *quoted* list of package names or IDs prefixed with + (install) or - (uninstall).\n" 528 + " mp-purge\t\tUninstall and remove all packages from the local cache.\n" 529 + " mp-hotfix\t\tInstall all the available hotfixes for the current platform but do not upgrade already installed ones (requires a registered instance).\n" 530 + " mp-upgrade\t\tGet all the available upgrades for the Nuxeo Packages currently installed (requires a registered instance).\n" 531 + " mp-show\t\t\tShow Nuxeo Package(s) information. You must provide the package file(s), name(s) or ID(s) as parameter.\n" 532 + " register\t\tRegister your instance with an existing Connect account. You must provide the credentials, the project name or ID, its type and a description.\n" 533 + " register-trial\t\tRegister your instance with a new trial Connect account. You must provide an email and a password as credentials, the company name, a project name and a description.\n" 534 + "\nThe following commands are always executed in console/headless mode (no GUI): " 535 + "\"configure\", \"mp-init\", \"mp-purge\", \"mp-add\", \"mp-install\", \"mp-uninstall\", \"mp-request\", " 536 + "\"mp-remove\", \"mp-hotfix\", \"mp-upgrade\", \"mp-reset\", \"mp-list\", \"mp-listall\", \"mp-update\", " 537 + "\"status\", \"showconf\", \"mp-show\", \"mp-set\", \"config\", \"encrypt\", \"decrypt\", \"help\".\n" 538 + "\nThe following commands cannot be executed on a running server: \"pack\", \"mp-init\", \"mp-purge\", " 539 + "\"mp-add\", \"mp-install\", \"mp-uninstall\", \"mp-request\", \"mp-remove\", \"mp-hotfix\", \"mp-upgrade\", " 540 + "\"mp-reset\".\n" 541 + "\nCommand parameters may need to be prefixed with '--' to separate them from option arguments when confusion arises."; 542 543 private static final String OPTION_HELP_USAGE = " nuxeoctl <command> [options] [--] [command parameters]\n\n"; 544 545 private static final String OPTION_HELP_HEADER = "SYNOPSIS\n" 546 + " nuxeoctl encrypt [--encrypt <algorithm>] [<clearValue>..] [-d [<categories>]|-q]\n" 547 + " Output encrypted value for <clearValue>.\n" 548 + " If <clearValue> is not provided, it is read from stdin.\n\n" 549 + " nuxeoctl decrypt '<cryptedValue>'.. [-d [<categories>]|-q]\n" // 550 + " Output decrypted value for <cryptedValue>. The secret key is read from stdin.\n\n" 551 + " nuxeoctl config [<key> <value>].. <key> [<value>] [--encrypt [<algorithm>]] [--set [<template>]] [-d [<categories>]|-q]\n" 552 + " Set template or global parameters.\n" 553 + " If <value> is not provided and the --set 'option' is used, then the value is read from stdin.\n\n" 554 + " nuxeoctl config [--get] <key>.. [-d [<categories>]|-q]\n" 555 + " Get value for the given key(s).\n\n" 556 + " nuxeoctl config [--get-regexp] <regexp>.. [-d [<categories>]|-q]\n" 557 + " Get value for the keys matching the given regular expression(s).\n\n" 558 + " nuxeoctl help|status|showconf [-d [<categories>]|-q]\n\n" 559 + " nuxeoctl configure [-d [<categories>]|-q|-hdw]\n\n" 560 + " nuxeoctl wizard [-d [<categories>]|-q|--clid <arg>|--gui <true|false|yes|no>]\n\n" 561 + " nuxeoctl stop [-d [<categories>]|-q|--gui <true|false|yes|no>]\n\n" 562 + " nuxeoctl start|restart|console|startbg|restartbg [-d [<categories>]|-q|--clid <arg>|--gui <true|false|yes|no>|--strict|-hdw]\n\n" 563 + " nuxeoctl mp-show [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json]\n\n" 564 + " nuxeoctl mp-list|mp-listall|mp-init|mp-update [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json]\n\n" 565 + " nuxeoctl mp-reset|mp-purge|mp-hotfix|mp-upgrade [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json|--accept <true|false|yes|no|ask>]\n\n" 566 + " nuxeoctl mp-add|mp-install|mp-uninstall|mp-remove|mp-set|mp-request [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json|--nodeps|--relax <true|false|yes|no|ask>|--accept <true|false|yes|no|ask>|-s|-im]\n\n" 567 + " nuxeoctl register [<username> [<project> [<type> <description>] [<pwd>]]]\n\n" 568 + " nuxeoctl register-trial [<email> <company> <project> <description> [<pwd>]]\n\n" 569 + " nuxeoctl pack <target> [-d [<categories>]|-q]\n\n" // 570 + "OPTIONS"; 571 572 private static final String OPTION_HELP_FOOTER = 573 "\nSee online documentation \"ADMINDOC/nuxeoctl and Control Panel Usage\": https://doc.nuxeo.com/x/FwNc"; 574 575 private static final int PAGE_SIZE = 20; 576 577 public static final String CONNECT_TC_URL = "https://www.nuxeo.com/legal/nuxeo-trial-terms-conditions"; 578 579 protected ConfigurationGenerator configurationGenerator; 580 581 public final ConfigurationGenerator getConfigurationGenerator() { 582 return configurationGenerator; 583 } 584 585 protected ProcessManager processManager; 586 587 protected Process nuxeoProcess; 588 589 private String processRegex; 590 591 protected String pid; 592 593 private ExecutorService executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("NuxeoProcessThread", 594 false)); 595 596 private ShutdownThread shutdownHook; 597 598 protected String[] params; 599 600 protected String command; 601 602 public String getCommand() { 603 return command; 604 } 605 606 /** 607 * @since 7.4 608 */ 609 public boolean commandIs(String aCommand) { 610 return StringUtils.equalsIgnoreCase(command, aCommand); 611 } 612 613 public CommandSetInfo cset = new CommandSetInfo(); 614 615 private boolean useGui = false; 616 617 /** 618 * @since 5.5 619 */ 620 public boolean isUsingGui() { 621 return useGui; 622 } 623 624 private boolean reloadConfiguration = false; 625 626 private int status = STATUS_CODE_UNKNOWN; 627 628 private int errorValue = EXIT_CODE_OK; 629 630 private StatusServletClient statusServletClient; 631 632 private static boolean quiet = false; 633 634 private static boolean debug = false; 635 636 private static boolean strict = false; 637 638 private boolean xmlOutput = false; 639 640 private boolean jsonOutput = false; 641 642 private ConnectBroker connectBroker = null; 643 644 private String clid = null; 645 646 private ConnectRegistrationBroker connectRegistrationBroker = null; 647 648 private InstanceInfo info; 649 650 CommandLine cmdLine; 651 652 private boolean ignoreMissing = false; 653 654 /** 655 * @since 5.5 656 * @return true if quiet mode is active 657 */ 658 public boolean isQuiet() { 659 return quiet; 660 } 661 662 private static Map<String, NuxeoLauncherGUI> guis; 663 664 /** 665 * @since 5.5 666 */ 667 public NuxeoLauncherGUI getGUI() { 668 if (guis == null) { 669 return null; 670 } 671 return guis.get(configurationGenerator.getNuxeoConf().toString()); 672 } 673 674 /** 675 * @since 5.5 676 */ 677 public void setGUI(NuxeoLauncherGUI gui) { 678 if (guis == null) { 679 guis = new HashMap<>(); 680 } 681 guis.put(configurationGenerator.getNuxeoConf().toString(), gui); 682 } 683 684 public NuxeoLauncher(ConfigurationGenerator configurationGenerator) { 685 this.configurationGenerator = configurationGenerator; 686 init(); 687 } 688 689 /** 690 * @since 5.6 691 */ 692 public void init() { 693 if (!configurationGenerator.init(true)) { 694 throw new IllegalStateException("Initialization failed"); 695 } 696 statusServletClient = new StatusServletClient(configurationGenerator); 697 statusServletClient.setKey(configurationGenerator.getUserConfig().getProperty(Environment.SERVER_STATUS_KEY)); 698 processManager = getOSProcessManager(); 699 processRegex = "^(?!/bin/sh).*" + Pattern.quote(configurationGenerator.getNuxeoConf().getPath()) + ".*" 700 + Pattern.quote(getServerPrint()) + ".*$"; 701 // Set OS-specific decorations 702 if (SystemUtils.IS_OS_MAC) { 703 System.setProperty("com.apple.mrj.application.apple.menu.about.name", "NuxeoCtl"); 704 } 705 } 706 707 private ProcessManager getOSProcessManager() { 708 if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_AIX) { 709 UnixProcessManager unixProcessManager = new UnixProcessManager(); 710 return unixProcessManager; 711 } else if (SystemUtils.IS_OS_MAC) { 712 return new MacProcessManager(); 713 } else if (SystemUtils.IS_OS_SUN_OS) { 714 return new SolarisProcessManager(); 715 } else if (SystemUtils.IS_OS_WINDOWS) { 716 WindowsProcessManager windowsProcessManager = new WindowsProcessManager(); 717 return windowsProcessManager.isUsable() ? windowsProcessManager : new PureJavaProcessManager(); 718 } else { 719 return new PureJavaProcessManager(); 720 } 721 } 722 723 /** 724 * Do not directly call this method without a call to 725 * {@link #checkNoRunningServer()} 726 * 727 * @see #doStart() 728 * @throws IOException 729 * In case of issue with process. 730 * @throws InterruptedException 731 * If any thread has interrupted the current thread. 732 */ 733 protected void start(boolean logProcessOutput) throws IOException, InterruptedException { 734 List<String> startCommand = new ArrayList<>(); 735 startCommand.add(getJavaExecutable().getPath()); 736 startCommand.addAll(getJavaOptsProperty(Function.identity())); 737 startCommand.add("-cp"); 738 startCommand.add(getClassPath()); 739 startCommand.addAll(getNuxeoProperties()); 740 startCommand.addAll(getServerProperties()); 741 setServerStartCommand(startCommand); 742 for (String param : params) { 743 startCommand.add(param); 744 } 745 ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand)); 746 pb.directory(configurationGenerator.getNuxeoHome()); 747 log.debug("Server command: " + pb.command()); 748 nuxeoProcess = pb.start(); 749 Thread.sleep(1000); 750 boolean processExited = false; 751 // Check if process exited early 752 if (nuxeoProcess == null) { 753 log.error(String.format("Server start failed with command: %s", pb.command())); 754 if (SystemUtils.IS_OS_WINDOWS && configurationGenerator.getNuxeoHome().getPath().contains(" ")) { 755 // NXP-17679 756 log.error("The server path must not contain spaces under Windows."); 757 } 758 return; 759 } 760 try { 761 int exitValue = nuxeoProcess.exitValue(); 762 if (exitValue != 0) { 763 log.error(String.format("Server start failed (%d).", exitValue)); 764 } 765 processExited = true; 766 } catch (IllegalThreadStateException e) { 767 // Normal case 768 } 769 logProcessStreams(nuxeoProcess, processExited || logProcessOutput); 770 if (!processExited) { 771 if (getPid() != null) { 772 log.warn("Server started with process ID " + pid + "."); 773 } else { 774 log.warn("Sent server start command but could not get process ID."); 775 } 776 } 777 } 778 779 /** 780 * Gets the Java options with 'nuxeo.*' properties substituted. It enables 781 * usage of property like ${nuxeo.log.dir} inside JAVA_OPTS. 782 * 783 * @return the java options string. 784 */ 785 protected List<String> getJavaOptsProperty(Function<String,String> mapper) { 786 return Arrays 787 .stream(System.getProperty(JAVA_OPTS_PROPERTY, JAVA_OPTS_DEFAULT) 788 .split("[ ]+(?=([^\"]*\"[^\"]*\")*[^\"]*$)")) 789 .map(opt -> StrSubstitutor.replace(opt, configurationGenerator.getUserConfig())) 790 .map(mapper) 791 .collect(Collectors.toList()); 792 } 793 794 /** 795 * Check if some server is already running (from another thread) and throw a 796 * Runtime exception if it finds one. That method will work where 797 * {@link #isRunning()} won't. 798 * 799 * @throws IllegalThreadStateException 800 * Thrown if a server is already running. 801 */ 802 public void checkNoRunningServer() throws IllegalStateException { 803 try { 804 String existingPid = getPid(); 805 if (existingPid != null) { 806 errorValue = EXIT_CODE_OK; 807 throw new IllegalStateException("A server is running with process ID " + existingPid); 808 } 809 } catch (IOException e) { 810 log.warn("Could not check existing process: " + e.getMessage()); 811 } 812 } 813 814 /** 815 * @return (since 5.5) Array list with created stream gobbler threads. 816 */ 817 public ArrayList<ThreadedStreamGobbler> logProcessStreams(Process process, boolean logProcessOutput) { 818 ArrayList<ThreadedStreamGobbler> sgArray = new ArrayList<>(); 819 ThreadedStreamGobbler inputSG, errorSG; 820 if (logProcessOutput) { 821 inputSG = new ThreadedStreamGobbler(process.getInputStream(), System.out); 822 errorSG = new ThreadedStreamGobbler(process.getErrorStream(), System.err); 823 } else { 824 inputSG = new ThreadedStreamGobbler(process.getInputStream(), SimpleLog.LOG_LEVEL_OFF); 825 errorSG = new ThreadedStreamGobbler(process.getErrorStream(), SimpleLog.LOG_LEVEL_OFF); 826 } 827 inputSG.start(); 828 errorSG.start(); 829 sgArray.add(inputSG); 830 sgArray.add(errorSG); 831 return sgArray; 832 } 833 834 protected abstract String getServerPrint(); 835 836 /** 837 * Will wrap, if necessary, the command within a Shell command 838 * 839 * @param roughCommand 840 * Java command which will be run 841 * @return wrapped command depending on the OS 842 */ 843 private List<String> getOSCommand(List<String> roughCommand) { 844 if (SystemUtils.IS_OS_UNIX) { 845 return getUnixCommand(roughCommand); 846 } 847 if (SystemUtils.IS_OS_WINDOWS) { 848 return getWindowsCommand(roughCommand); 849 } 850 throw new IllegalStateException("Unkown os, can't launch server"); 851 } 852 853 private List<String> getWindowsCommand(List<String> roughCommand) { 854 ArrayList<String> osCommand = new ArrayList<>(); 855 for (String commandToken : roughCommand) { 856 if (StringUtils.isBlank(commandToken)) { 857 continue; 858 } 859 osCommand.add("\"" + commandToken + "\""); 860 } 861 return osCommand; 862 } 863 864 private List<String> getUnixCommand(List<String> roughCommand) { 865 ArrayList<String> osCommand = new ArrayList<>(); 866 String linearizedCommand = new String(); 867 for (String commandToken : roughCommand) { 868 if (StringUtils.isBlank(commandToken)) { 869 continue; 870 } 871 if (commandToken.contains(" ")) { 872 commandToken = commandToken.replaceAll(" ", "\\\\ "); 873 } 874 linearizedCommand += " " + commandToken; 875 } 876 osCommand.add("/bin/sh"); 877 osCommand.add("-c"); 878 osCommand.add(linearizedCommand); 879 return osCommand; 880 } 881 882 protected abstract Collection<? extends String> getServerProperties(); 883 884 protected abstract void setServerStartCommand(List<String> command); 885 886 private File getJavaExecutable() { 887 File javaExec = new File(System.getProperty("java.home"), "bin" + File.separator + "java"); 888 return javaExec; 889 } 890 891 protected abstract String getClassPath(); 892 893 /** 894 * @since 5.6 895 */ 896 protected abstract String getShutdownClassPath(); 897 898 protected Collection<? extends String> getNuxeoProperties() { 899 ArrayList<String> nuxeoProperties = new ArrayList<>(); 900 nuxeoProperties.add(String.format("-D%s=%s", Environment.NUXEO_HOME, configurationGenerator.getNuxeoHome() 901 .getPath())); 902 nuxeoProperties.add(String.format("-D%s=%s", ConfigurationGenerator.NUXEO_CONF, 903 configurationGenerator.getNuxeoConf().getPath())); 904 nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_LOG_DIR)); 905 nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_DATA_DIR)); 906 nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_TMP_DIR)); 907 nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_MP_DIR)); 908 if (!DEFAULT_NUXEO_CONTEXT_PATH.equals(configurationGenerator.getUserConfig().getProperty( 909 Environment.NUXEO_CONTEXT_PATH))) { 910 nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_CONTEXT_PATH)); 911 } 912 if (overrideJavaTmpDir) { 913 nuxeoProperties.add("-Djava.io.tmpdir=" 914 + configurationGenerator.getUserConfig().getProperty(Environment.NUXEO_TMP_DIR)); 915 } 916 return nuxeoProperties; 917 } 918 919 private String getNuxeoProperty(String property) { 920 return "-D" + property + "=" + configurationGenerator.getUserConfig().getProperty(property); 921 } 922 923 protected String addToClassPath(String cp, String filename) { 924 File classPathEntry = new File(configurationGenerator.getNuxeoHome(), filename); 925 if (!classPathEntry.exists()) { 926 classPathEntry = new File(filename); 927 } 928 if (!classPathEntry.exists()) { 929 throw new RuntimeException("Tried to add inexistent classpath entry: " + filename); 930 } 931 cp += System.getProperty("path.separator") + classPathEntry.getPath(); 932 return cp; 933 } 934 935 /** 936 * @since 5.6 937 */ 938 protected static Options initParserOptions() { 939 Options options = new Options(); 940 // help option 941 options.addOption(Option.builder("h").longOpt(OPTION_HELP).desc(OPTION_HELP_DESC).build()); 942 // Quiet option 943 options.addOption(Option.builder("q").longOpt(OPTION_QUIET).desc(OPTION_QUIET_DESC).build()); 944 { // Debug options (mutually exclusive) 945 OptionGroup debugOptions = new OptionGroup(); 946 // Debug option 947 debugOptions.addOption(Option.builder("d") 948 .longOpt(OPTION_DEBUG) 949 .desc(OPTION_DEBUG_DESC) 950 .hasArgs() 951 .argName(OPTION_DEBUG_CATEGORY_ARG_NAME) 952 .optionalArg(true) 953 .valueSeparator(',') 954 .build()); 955 // Debug category option 956 debugOptions.addOption(Option.builder(OPTION_DEBUG_CATEGORY) 957 .desc(OPTION_DEBUG_CATEGORY_DESC) 958 .hasArgs() 959 .argName(OPTION_DEBUG_CATEGORY_ARG_NAME) 960 .optionalArg(true) 961 .valueSeparator(',') 962 .build()); 963 options.addOptionGroup(debugOptions); 964 } 965 // For help output purpose only: that option is managed and 966 // swallowed by the nuxeoctl Shell script 967 options.addOption(Option.builder() 968 .longOpt("debug-launcher") 969 .desc("Linux-only. Activate Java debugging mode on the Launcher.") 970 .build()); 971 // Instance CLID option 972 options.addOption(Option.builder().longOpt(OPTION_CLID).desc(OPTION_CLID_DESC).hasArg().build()); 973 { // Output options (mutually exclusive) 974 OptionGroup outputOptions = new OptionGroup(); 975 // XML option 976 outputOptions.addOption(Option.builder().longOpt(OPTION_XML).desc(OPTION_XML_DESC).build()); 977 // JSON option 978 outputOptions.addOption(Option.builder().longOpt(OPTION_JSON).desc(OPTION_JSON_DESC).build()); 979 options.addOptionGroup(outputOptions); 980 } 981 // GUI option 982 options.addOption(Option.builder() 983 .longOpt(OPTION_GUI) 984 .desc(OPTION_GUI_DESC) 985 .hasArg() 986 .argName("true|false|yes|no") 987 .build()); 988 // Package management option 989 options.addOption(Option.builder().longOpt(OPTION_NODEPS).desc(OPTION_NODEPS_DESC).build()); 990 // Relax on target platform option 991 options.addOption(Option.builder() 992 .longOpt(OPTION_RELAX) 993 .desc(OPTION_RELAX_DESC) 994 .hasArg() 995 .argName("true|false|yes|no|ask") 996 .build()); 997 // Accept option 998 options.addOption(Option.builder() 999 .longOpt(OPTION_ACCEPT) 1000 .desc(OPTION_ACCEPT_DESC) 1001 .hasArg() 1002 .argName("true|false|yes|no|ask") 1003 .build()); 1004 // Allow SNAPSHOT option 1005 options.addOption(Option.builder("s").longOpt(OPTION_SNAPSHOT).desc(OPTION_SNAPSHOT_DESC).build()); 1006 // Force option 1007 options.addOption(Option.builder("f").longOpt(OPTION_FORCE).desc(OPTION_FORCE_DESC).build()); 1008 // Strict option 1009 options.addOption(Option.builder().longOpt(OPTION_STRICT).desc(OPTION_STRICT_DESC).build()); 1010 1011 // Ignore missing option 1012 options.addOption(Option.builder("im") 1013 .longOpt(OPTION_IGNORE_MISSING) 1014 .desc(OPTION_IGNORE_MISSING_DESC) 1015 .build()); 1016 // Hide deprecation warnings option 1017 options.addOption(Option.builder("hdw") 1018 .longOpt(OPTION_HIDE_DEPRECATION) 1019 .desc(OPTION_HIDE_DEPRECATION_DESC) 1020 .build()); 1021 // Encrypt option 1022 options.addOption(Option.builder() 1023 .longOpt(OPTION_ENCRYPT) 1024 .desc(OPTION_ENCRYPT_DESC) 1025 .hasArg() 1026 .argName(OPTION_ENCRYPT_ARG_NAME) 1027 .optionalArg(true) 1028 .build()); 1029 // Output options 1030 options.addOption(Option.builder() 1031 .longOpt(OPTION_GZIP_OUTPUT) 1032 .desc(OPTION_GZIP_DESC) 1033 .hasArg() 1034 .argName("true|false") 1035 .optionalArg(true) 1036 .build()); 1037 options.addOption(Option.builder() 1038 .longOpt(OPTION_PRETTY_PRINT) 1039 .desc(OPTION_PRETTY_PRINT_DESC) 1040 .hasArg() 1041 .argName("true|false") 1042 .optionalArg(true) 1043 .build()); 1044 options.addOption(Option.builder() 1045 .longOpt(OPTION_OUTPUT) 1046 .desc(OPTION_OUTPUT_DESC) 1047 .hasArg() 1048 .argName("file") 1049 .optionalArg(true) 1050 .build()); 1051 { // Config options (mutually exclusive) 1052 OptionGroup configOptions = new OptionGroup(); 1053 // Set option 1054 configOptions.addOption(Option.builder() 1055 .longOpt(OPTION_SET) 1056 .desc(OPTION_SET_DESC) 1057 .hasArg() 1058 .argName(OPTION_SET_ARG_NAME) 1059 .optionalArg(true) 1060 .build()); 1061 configOptions.addOption(Option.builder() 1062 .longOpt(OPTION_GET) 1063 .desc(OPTION_GET_DESC) 1064 .build()); 1065 configOptions.addOption(Option.builder() 1066 .longOpt(OPTION_GET_REGEXP) 1067 .desc(OPTION_GET_REGEXP_DESC) 1068 .build()); 1069 options.addOptionGroup(configOptions); 1070 } 1071 return options; 1072 } 1073 1074 /** 1075 * @since 5.6 1076 */ 1077 protected static CommandLine parseOptions(String[] args) throws ParseException { 1078 CommandLineParser parser = new DefaultParser(); 1079 CommandLine cmdLine = null; 1080 cmdLine = parser.parse(options, args); 1081 if (cmdLine.hasOption(OPTION_HELP)) { 1082 cmdLine.getArgList().add(OPTION_HELP); 1083 setQuiet(); 1084 } else if (cmdLine.getArgList().isEmpty()) { 1085 throw new ParseException("Missing command."); 1086 } 1087 // Common options to the Launcher and the ConfigurationGenerator 1088 if (cmdLine.hasOption(OPTION_QUIET) || cmdLine.hasOption(OPTION_XML) || cmdLine.hasOption(OPTION_JSON)) { 1089 setQuiet(); 1090 } 1091 if (cmdLine.hasOption(OPTION_DEBUG)) { 1092 setDebug(cmdLine.getOptionValues(OPTION_DEBUG), "org.nuxeo.launcher"); 1093 } 1094 if (cmdLine.hasOption(OPTION_DEBUG_CATEGORY)) { 1095 setDebug(cmdLine.getOptionValues(OPTION_DEBUG_CATEGORY), "org.nuxeo.launcher"); 1096 } 1097 if (cmdLine.hasOption(OPTION_FORCE) || cmdLine.hasOption(OPTION_STRICT)) { 1098 setStrict(true); 1099 } 1100 return cmdLine; 1101 } 1102 1103 public static void main(String[] args) { 1104 NuxeoLauncher launcher = null; 1105 try { 1106 launcher = createLauncher(args); 1107 if (launcher.commandRequiresNoGUI()) { 1108 launcher.useGui = false; 1109 } 1110 if (launcher.useGui && launcher.getGUI() == null) { 1111 launcher.setGUI(new NuxeoLauncherGUI(launcher)); 1112 } 1113 launch(launcher); 1114 } catch (ParseException e) { 1115 log.error("Invalid command line. " + e.getMessage()); 1116 log.debug(e, e); 1117 printShortHelp(); 1118 System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID 1119 : launcher.errorValue); 1120 } catch (IOException | PackageException | ConfigurationException | GeneralSecurityException e) { 1121 log.error(e.getMessage()); 1122 log.debug(e, e); 1123 System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID 1124 : launcher.errorValue); 1125 } catch (Exception e) { 1126 log.error("Cannot execute command. " + e.getMessage(), e); 1127 log.debug(e, e); 1128 System.exit(EXIT_CODE_ERROR); 1129 } 1130 } 1131 1132 /** 1133 * @since 5.5 1134 * @param launcher 1135 * @throws PackageException 1136 * @throws IOException 1137 * @throws ConfigurationException 1138 * @throws ParseException 1139 * @throws GeneralSecurityException 1140 */ 1141 public static void launch(final NuxeoLauncher launcher) throws IOException, PackageException, 1142 ConfigurationException, ParseException, GeneralSecurityException { 1143 boolean commandSucceeded = true; 1144 if (launcher.commandIs(null)) { 1145 return; 1146 } 1147 if (launcher.commandRequiresNoRunningServer()) { 1148 launcher.checkNoRunningServer(); 1149 } 1150 if (launcher.commandIs(OPTION_HELP)) { 1151 printLongHelp(); 1152 } else if (launcher.commandIs("status")) { 1153 String statusMsg = launcher.status(); 1154 launcher.errorValue = launcher.getStatus(); 1155 if (!quiet) { 1156 log.warn(statusMsg); 1157 if (launcher.isStarted()) { 1158 log.info("Go to " + launcher.getURL()); 1159 log.info(launcher.getStartupSummary()); 1160 } 1161 } 1162 } else if (launcher.commandIs("startbg")) { 1163 commandSucceeded = launcher.doStart(); 1164 } else if (launcher.commandIs("start")) { 1165 if (launcher.useGui) { 1166 launcher.getGUI().start(); 1167 } else { 1168 commandSucceeded = launcher.doStartAndWait(); 1169 } 1170 } else if (launcher.commandIs("console")) { 1171 launcher.executor.execute(new Runnable() { 1172 @Override 1173 public void run() { 1174 launcher.addShutdownHook(); 1175 try { 1176 if (!launcher.doStart(true)) { 1177 launcher.removeShutdownHook(); 1178 System.exit(1); 1179 } else if (!quiet) { 1180 log.info("Go to " + launcher.getURL()); 1181 } 1182 } catch (PackageException e) { 1183 log.error("Could not initialize the packaging subsystem", e); 1184 launcher.removeShutdownHook(); 1185 System.exit(EXIT_CODE_ERROR); 1186 } 1187 } 1188 }); 1189 } else if (launcher.commandIs("stop")) { 1190 if (launcher.useGui) { 1191 launcher.getGUI().stop(); 1192 } else { 1193 launcher.stop(); 1194 } 1195 } else if (launcher.commandIs("restartbg")) { 1196 launcher.stop(); 1197 commandSucceeded = launcher.doStart(); 1198 } else if (launcher.commandIs("restart")) { 1199 launcher.stop(); 1200 commandSucceeded = launcher.doStartAndWait(); 1201 } else if (launcher.commandIs("wizard")) { 1202 commandSucceeded = launcher.startWizard(); 1203 } else if (launcher.commandIs("configure")) { 1204 launcher.configure(); 1205 } else if (launcher.commandIs("pack")) { 1206 launcher.pack(); 1207 } else if (launcher.commandIs("mp-list")) { 1208 launcher.pkgList(); 1209 } else if (launcher.commandIs("mp-listall")) { 1210 launcher.pkgListAll(); 1211 } else if (launcher.commandIs("mp-init")) { 1212 commandSucceeded = launcher.pkgInit(); 1213 } else if (launcher.commandIs("mp-purge")) { 1214 commandSucceeded = launcher.pkgPurge(); 1215 } else if (launcher.commandIs("mp-add")) { 1216 if (launcher.cmdLine.hasOption(OPTION_NODEPS)) { 1217 commandSucceeded = launcher.pkgAdd(launcher.params); 1218 } else { 1219 commandSucceeded = launcher.pkgRequest(Arrays.asList(launcher.params), null, null, null); 1220 } 1221 } else if (launcher.commandIs("mp-install")) { 1222 if (launcher.cmdLine.hasOption(OPTION_NODEPS)) { 1223 commandSucceeded = launcher.pkgInstall(launcher.params); 1224 } else { 1225 commandSucceeded = launcher.pkgRequest(null, Arrays.asList(launcher.params), null, null); 1226 } 1227 } else if (launcher.commandIs("mp-uninstall")) { 1228 if (launcher.cmdLine.hasOption(OPTION_NODEPS)) { 1229 commandSucceeded = launcher.pkgUninstall(launcher.params); 1230 } else { 1231 commandSucceeded = launcher.pkgRequest(null, null, Arrays.asList(launcher.params), null); 1232 } 1233 } else if (launcher.commandIs("mp-remove")) { 1234 if (launcher.cmdLine.hasOption(OPTION_NODEPS)) { 1235 commandSucceeded = launcher.pkgRemove(launcher.params); 1236 } else { 1237 commandSucceeded = launcher.pkgRequest(null, null, null, Arrays.asList(launcher.params)); 1238 } 1239 } else if (launcher.commandIs("mp-request")) { 1240 if (launcher.cmdLine.hasOption(OPTION_NODEPS)) { 1241 throw new ParseException("The command mp-request is not available with the --nodeps option"); 1242 } else { 1243 commandSucceeded = launcher.pkgCompoundRequest(Arrays.asList(launcher.params)); 1244 } 1245 } else if (launcher.commandIs("mp-set")) { 1246 commandSucceeded = launcher.pkgSetRequest(Arrays.asList(launcher.params), 1247 launcher.cmdLine.hasOption(OPTION_NODEPS)); 1248 } else if (launcher.commandIs("mp-hotfix")) { 1249 commandSucceeded = launcher.pkgHotfix(); 1250 } else if (launcher.commandIs("mp-upgrade")) { 1251 commandSucceeded = launcher.pkgUpgrade(); 1252 } else if (launcher.commandIs("mp-reset")) { 1253 commandSucceeded = launcher.pkgReset(); 1254 } else if (launcher.commandIs("mp-update")) { 1255 commandSucceeded = launcher.pkgRefreshCache(); 1256 } else if (launcher.commandIs("showconf")) { 1257 launcher.showConfig(); 1258 } else if (launcher.commandIs("mp-show")) { 1259 commandSucceeded = launcher.pkgShow(launcher.params); 1260 } else if (launcher.commandIs("encrypt")) { 1261 launcher.encrypt(); 1262 } else if (launcher.commandIs("decrypt")) { 1263 launcher.decrypt(); 1264 } else if (launcher.commandIs("config")) { 1265 launcher.config(); 1266 } else if (launcher.commandIs("register")) { 1267 commandSucceeded = launcher.registerRemoteInstance(); 1268 } else if (launcher.commandIs("register-trial")) { 1269 commandSucceeded = launcher.registerTrial(); 1270 } else if (launcher.commandIs("connect-report")) { 1271 boolean gzip = Boolean.valueOf(launcher.cmdLine.getOptionValue(OPTION_GZIP_OUTPUT, "true")).booleanValue(); 1272 boolean prettyprinting = Boolean.valueOf(launcher.cmdLine.getOptionValue(OPTION_PRETTY_PRINT, "false")); 1273 Path outputpath; 1274 if (launcher.cmdLine.hasOption(OPTION_OUTPUT)) { 1275 outputpath = Paths.get(launcher.cmdLine.getOptionValue(OPTION_OUTPUT)); 1276 } else { 1277 Path dir = Paths.get(launcher.configurationGenerator.getUserConfig().getProperty(Environment.NUXEO_TMP_DIR)); 1278 outputpath = dir.resolve("nuxeo-connect-tools-report.json".concat( 1279 gzip ? ".gz" : "")); 1280 } 1281 log.info("Dumping connect report in " + outputpath); 1282 try (OutputStream output = openOutput(outputpath, gzip)) { 1283 commandSucceeded = launcher.dumpConnectReport(output,prettyprinting); 1284 } 1285 } else { 1286 log.error("Unknown command " + launcher.command); 1287 printLongHelp(); 1288 launcher.errorValue = EXIT_CODE_INVALID; 1289 } 1290 if (launcher.xmlOutput && launcher.command.startsWith("mp-")) { 1291 launcher.printXMLOutput(); 1292 } 1293 commandSucceeded = commandSucceeded && launcher.errorValue == EXIT_CODE_OK; 1294 if (!commandSucceeded && !quiet || debug) { 1295 launcher.cset.log(commandSucceeded && debug); 1296 } 1297 if (!commandSucceeded) { 1298 System.exit(launcher.errorValue); 1299 } 1300 } 1301 1302 protected static OutputStream openOutput(Path path, boolean gzip) throws IOException { 1303 OutputStream output = Files.newOutputStream(path, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); 1304 if (gzip) { 1305 output = new GZIPOutputStream(output); 1306 } 1307 return output; 1308 } 1309 1310 /** 1311 * Prompts for a valid email address according to RFC 822 standards. The 1312 * remote service may apply stricter constraints on email validation such as 1313 * some black listed domains. 1314 * 1315 * @return the user input. Never null. 1316 * @throws ConfigurationException 1317 * If the user input is read from stdin and is {@code null} or 1318 * does not match the {@code regex} 1319 * @since 8.3 1320 */ 1321 public String promptEmail() throws IOException, ConfigurationException { 1322 EmailValidator validator = EmailValidator.getInstance(); 1323 final String message = "Email Address: "; 1324 final String error = "Invalid email address."; 1325 return prompt(message, validator::isValid, error); 1326 } 1327 1328 /** 1329 * @since 8.3 1330 */ 1331 public String promptDescription() throws ConfigurationException, IOException { 1332 return prompt("Description: ", null, null); 1333 } 1334 1335 /** 1336 * Prompt for a value read from the console or stdin. 1337 * 1338 * @param message 1339 * message to display at prompt 1340 * @param predicate 1341 * a predicate that must match a correct user input. Ignored if 1342 * {@code null}. 1343 * @param error 1344 * an error message to display or raise when the user input is 1345 * {@code null} or does not match the {@code regex} 1346 * @return the user input. Never null. 1347 * @throws ConfigurationException 1348 * If the user input is read from stdin and is {@code null} or 1349 * does not match the {@code regex} 1350 * @since 8.3 1351 */ 1352 public String prompt(String message, Predicate<String> predicate, String error) throws IOException, 1353 ConfigurationException { 1354 boolean doRegexMatch = predicate != null; 1355 String value; 1356 Console console = System.console(); 1357 if (console != null) { 1358 value = console.readLine(message); 1359 while (value == null || doRegexMatch && !predicate.test(value)) { 1360 console.printf(error + "\n", value); 1361 value = console.readLine(message); 1362 } 1363 } else { // try reading from stdin 1364 value = IOUtils.toString(System.in); 1365 if (value == null || doRegexMatch && !predicate.test(value)) { 1366 throw new ConfigurationException(error); 1367 } 1368 } 1369 return value; 1370 } 1371 1372 /** 1373 * @param message 1374 * message to display at prompt 1375 * @since 8.3 1376 */ 1377 public char[] promptPassword(String message) throws IOException { 1378 Console console = System.console(); 1379 if (console != null) { 1380 return console.readPassword(message); 1381 } else { // try reading from stdin 1382 return IOUtils.toCharArray(System.in); 1383 } 1384 } 1385 1386 /** 1387 * @param confirmation 1388 * if true, password is asked twice. 1389 * @since 8.3 1390 */ 1391 public char[] promptPassword(boolean confirmation) throws IOException, ConfigurationException { 1392 char[] pwd = promptPassword("Please enter your password: "); 1393 if (confirmation) { 1394 char[] pwdVerification = promptPassword("Please re-enter your password: "); 1395 if (!Arrays.equals(pwd, pwdVerification)) { 1396 throw new ConfigurationException("Passwords do not match."); 1397 } 1398 } 1399 return pwd; 1400 } 1401 1402 /** 1403 * @return a {@link NuxeoClientInstanceType}. Never {@code null}. 1404 * @since 8.3 1405 */ 1406 public NuxeoClientInstanceType promptInstanceType() throws IOException, ConfigurationException { 1407 NuxeoClientInstanceType type; 1408 Console console = System.console(); 1409 if (console == null) { 1410 String typeStr = IOUtils.toString(System.in); 1411 type = NuxeoClientInstanceType.fromString(typeStr); 1412 if (type == null) { 1413 throw new ConfigurationException("Unknown type: " + typeStr); 1414 } 1415 return type; 1416 } 1417 do { 1418 String s = console.readLine("Instance type (dev|preprod|prod): [dev] "); 1419 if (StringUtils.isBlank(s)) { 1420 type = NuxeoClientInstanceType.DEV; 1421 } else { 1422 type = NuxeoClientInstanceType.fromString(s); 1423 } 1424 } while (type == null); 1425 return type; 1426 } 1427 1428 /** 1429 * @since 8.3 1430 */ 1431 public boolean promptAcceptTerms() { 1432 Console console = System.console(); 1433 if (console != null) { 1434 String terms = console.readLine("Read and accept the Nuxeo Trial Terms and Conditions - " + CONNECT_TC_URL 1435 + " (yes/no)? [yes] "); 1436 if (StringUtils.isEmpty(terms)) { 1437 terms = "yes"; 1438 } 1439 terms = terms.trim().toLowerCase(); 1440 return "y".equalsIgnoreCase(terms) || "yes".equalsIgnoreCase(terms); 1441 } else { 1442 log.info("Read Nuxeo Trial Terms and Conditions - " + CONNECT_TC_URL); 1443 return true; 1444 } 1445 } 1446 1447 /** 1448 * @param projects 1449 * available projects the user must choose one amongst. 1450 * @return a project. Never null. 1451 * @throws ConfigurationException 1452 * If {@code projects} is empty or if there is not such a 1453 * project named as the parameter read from stdin. 1454 * @since 8.3 1455 */ 1456 public ConnectProject promptProject(@NotNull List<ConnectProject> projects) throws ConfigurationException, 1457 IOException, PackageException { 1458 if (projects.isEmpty()) { 1459 throw new ConfigurationException("You don't have access to any project."); 1460 } 1461 if (projects.size() == 1) { 1462 return projects.get(0); 1463 } 1464 1465 String projectName; 1466 Console console = System.console(); 1467 if (console == null) { 1468 projectName = IOUtils.toString(System.in); 1469 ConnectProject project = getConnectRegistrationBroker().getProjectByName(projectName, projects); 1470 if (project == null) { 1471 throw new ConfigurationException("Unknown project: " + projectName); 1472 } 1473 return project; 1474 } 1475 1476 System.out.println("Available projects:"); 1477 int i = 0; 1478 boolean hasNextPage = true; 1479 while (true) { 1480 if (i > 0 && !SystemUtils.IS_OS_WINDOWS) { 1481 // Remove last line to only have projects 1482 System.out.print("\33[1A\33[2K"); 1483 } 1484 1485 int fromIndex = i * PAGE_SIZE; 1486 int toIndex = (i + 1) * PAGE_SIZE; 1487 if (toIndex >= projects.size()) { 1488 toIndex = projects.size(); 1489 hasNextPage = false; 1490 } 1491 1492 projects.subList(fromIndex, toIndex).forEach( 1493 project -> System.out.println("\t- " + project.getSymbolicName())); 1494 if (toIndex < projects.size()) { 1495 int pageLeft = (int) Math.ceil((projects.size() - (i * PAGE_SIZE)) / PAGE_SIZE); 1496 System.out.print(String.format("Project name (press Enter for next page; %d pages left): ", pageLeft)); 1497 } else { 1498 System.out.print("Project name: "); 1499 } 1500 if (hasNextPage) { 1501 i++; 1502 } 1503 projectName = console.readLine(); 1504 if (StringUtils.isNotEmpty(projectName)) { 1505 ConnectProject project = getConnectRegistrationBroker().getProjectByName(projectName, projects); 1506 if (project != null) { 1507 return project; 1508 } 1509 System.err.println("Unknown project: " + projectName); 1510 i = 0; 1511 hasNextPage = true; 1512 } 1513 } 1514 } 1515 1516 /** 1517 * Register the instance, generating the CLID. 1518 * 1519 * <pre> 1520 * {@code 1521 * nuxeoctl register [<username> [<project> [<type> <description>] [pwd]]] 1522 * 0/1 param: [<username>] 1523 * 2/3 params: <username> <project> [pwd] 1524 * 4/5 params: <username> <project> <type> <description> [pwd] 1525 * } 1526 * </pre> 1527 * 1528 * Missing parameters are read from stdin. 1529 * 1530 * @return true if succeed 1531 * @since 8.3 1532 */ 1533 public boolean registerRemoteInstance() throws IOException, ConfigurationException, PackageException { 1534 if (params.length > 5) { 1535 throw new ConfigurationException("Wrong number of arguments."); 1536 } 1537 String username; 1538 if (params.length > 0) { 1539 username = params[0]; 1540 } else { 1541 username = prompt("Username: ", s -> StringUtils.isNotBlank(s), "Username cannot be empty."); 1542 } 1543 char[] password; 1544 if (params.length == 3 || params.length == 5) { 1545 password = params[params.length - 1].toCharArray(); 1546 } else { 1547 password = promptPassword(false); 1548 } 1549 ConnectProject project; 1550 List<ConnectProject> projs = getConnectRegistrationBroker().getAvailableProjects(username, password); 1551 if (params.length > 1) { 1552 String projectName = params[1]; 1553 project = getConnectRegistrationBroker().getProjectByName(projectName, projs); 1554 if (project == null) { 1555 throw new ConfigurationException("Unknown project: " + projectName); 1556 } 1557 } else { 1558 project = promptProject(projs); 1559 } 1560 NuxeoClientInstanceType type; 1561 String description; 1562 if (params.length > 3) { 1563 type = NuxeoClientInstanceType.fromString(params[2]); 1564 if (type == null) { 1565 throw new ConfigurationException("Unknown type: " + params[2]); 1566 } 1567 description = params[3]; 1568 } else { 1569 type = promptInstanceType(); 1570 description = promptDescription(); 1571 } 1572 1573 getConnectRegistrationBroker().registerRemote(username, password, project.getUuid(), type, description); 1574 log.info(String.format("Server registered to %s for project %s\nType: %s\nDescription: %s", username, project, 1575 type, description)); 1576 return true; 1577 } 1578 1579 /** 1580 * @since 8.3 1581 */ 1582 public boolean registerTrial() throws IOException, ConfigurationException, PackageException { 1583 CommandInfo commandInfo = cset.newCommandInfo("register-trial"); 1584 if (params.length != 0 && params.length != 4 && params.length != 5) { 1585 throw new ConfigurationException("Wrong number of arguments."); 1586 } 1587 1588 Map<String, String> registration = new HashMap<>(); 1589 if (params.length > 3) { 1590 String email = params[0]; 1591 if (!EmailValidator.getInstance().isValid(email)) { 1592 throw new ConfigurationException("Invalid email address."); 1593 } 1594 registration.put(REGISTRATION_EMAIL, email); 1595 1596 String company = params[1]; 1597 if (!Pattern.matches(REGISTRATION_COMPANY_REGEX, company)) { 1598 throw new ConfigurationException("Company field is mandatory."); 1599 } 1600 registration.put(REGISTRATION_COMPANY, company); 1601 1602 String projectName = params[2]; 1603 if (!Pattern.matches(REGISTRATION_PROJECT_REGEX, projectName)) { 1604 throw new ConfigurationException("Project name can only contain alphanumeric characters and dashes."); 1605 } 1606 registration.put(REGISTRATION_PROJECT, projectName); 1607 1608 registration.put(REGISTRATION_DESCRIPTION, params[3]); 1609 } else { 1610 registration.put(REGISTRATION_EMAIL, promptEmail()); 1611 registration.put(REGISTRATION_COMPANY, 1612 prompt("Company: ", s -> s.matches(REGISTRATION_COMPANY_REGEX), "Company field is mandatory.")); 1613 registration.put( 1614 REGISTRATION_PROJECT, 1615 prompt("Project name: ", s -> s.matches(REGISTRATION_PROJECT_REGEX), 1616 "Project name can only contain alphanumeric characters and dashes.")); 1617 registration.put("description", promptDescription()); 1618 } 1619 1620 if (params.length == 5) { 1621 registration.put(REGISTRATION_PASSWORD, params[4]); 1622 registration.put(REGISTRATION_PASSWORDND, params[4]); 1623 } else { 1624 // TODO NXP-19258 remove password_verif 1625 char[] pwd = promptPassword(true); 1626 registration.put(REGISTRATION_PASSWORD, new String(pwd)); 1627 registration.put(REGISTRATION_PASSWORDND, new String(pwd)); 1628 } 1629 1630 if (!promptAcceptTerms()) { 1631 log.error("You must accept the Nuxeo Trial Terms and Conditions to register your instance."); 1632 errorValue = EXIT_CODE_INVALID; 1633 commandInfo.exitCode = 1; 1634 return false; 1635 } 1636 registration.put(REGISTRATION_TERMSNCONDITIONS, "true"); 1637 1638 try { 1639 getConnectRegistrationBroker().registerTrial(registration); 1640 } catch (RegistrationException e) { 1641 commandInfo.newMessage(e); 1642 e.getErrors().forEach(err -> commandInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, err.getMessage())); 1643 errorValue = EXIT_CODE_NOT_CONFIGURED; 1644 commandInfo.exitCode = 1; 1645 return false; 1646 } 1647 log.info(String.format( 1648 "Trial registered to %s for project %s\nDescription: %s\n" 1649 + "Please ensure you have validated your registration with the confirmation email before starting the server.", 1650 registration.get(REGISTRATION_EMAIL), registration.get(REGISTRATION_PROJECT), 1651 registration.get(REGISTRATION_DESCRIPTION))); 1652 return true; 1653 } 1654 1655 /** 1656 * @throws ConfigurationException 1657 * @throws GeneralSecurityException 1658 * @since 7.4 1659 */ 1660 protected void encrypt() throws ConfigurationException, GeneralSecurityException { 1661 Crypto crypto = configurationGenerator.getCrypto(); 1662 String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null); 1663 if (params.length == 0) { 1664 Console console = System.console(); 1665 String encryptedString; 1666 if (console != null) { 1667 encryptedString = crypto.encrypt(algorithm, 1668 Crypto.getBytes(console.readPassword("Please enter the value to encrypt: "))); 1669 } else { // try reading from stdin 1670 try { 1671 encryptedString = crypto.encrypt(algorithm, IOUtils.toByteArray(System.in)); 1672 } catch (IOException e) { 1673 log.debug(e, e); 1674 errorValue = EXIT_CODE_ERROR; 1675 return; 1676 } 1677 } 1678 System.out.println(encryptedString); 1679 } else { 1680 for (String strToEncrypt : params) { 1681 String encryptedString = crypto.encrypt(algorithm, strToEncrypt.getBytes()); 1682 System.out.println(encryptedString); 1683 } 1684 } 1685 } 1686 1687 /** 1688 * @throws ConfigurationException 1689 * @since 7.4 1690 */ 1691 protected void decrypt() throws ConfigurationException { 1692 Crypto crypto = configurationGenerator.getCrypto(); 1693 boolean validKey = false; 1694 Console console = System.console(); 1695 if (console != null) { 1696 validKey = crypto.verifyKey(console.readPassword("Please enter the secret key: ")); 1697 } else { // try reading from stdin 1698 try { 1699 validKey = crypto.verifyKey(IOUtils.toByteArray(System.in)); 1700 } catch (IOException e) { 1701 log.debug(e, e); 1702 errorValue = EXIT_CODE_ERROR; 1703 return; 1704 } 1705 } 1706 if (!validKey) { 1707 errorValue = EXIT_CODE_INVALID; 1708 return; 1709 } 1710 for (String strToDecrypt : params) { 1711 System.out.println(Crypto.getChars(crypto.decrypt(strToDecrypt))); 1712 } 1713 } 1714 1715 /** 1716 * @throws ConfigurationException 1717 * @throws IOException 1718 * @throws GeneralSecurityException 1719 * @since 7.4 1720 */ 1721 protected void config() throws ConfigurationException, IOException, GeneralSecurityException { 1722 if (cmdLine.hasOption(OPTION_SET) || !cmdLine.hasOption(OPTION_GET) && !cmdLine.hasOption(OPTION_GET_REGEXP) 1723 && params.length == 2) { 1724 setConfigProperties(); 1725 } else { // OPTION_GET || OPTION_GET_REGEXP || !OPTION_SET && 1726 // params.length != 2 1727 getConfigProperties(); 1728 } 1729 } 1730 1731 /** 1732 * @since 7.4 1733 */ 1734 protected void getConfigProperties() { 1735 boolean isRegexp = cmdLine.hasOption(OPTION_GET_REGEXP); 1736 CryptoProperties userConfig = configurationGenerator.getUserConfig(); 1737 List<String> keys; 1738 if (isRegexp) { 1739 keys = new ArrayList<>(); 1740 for (Object key : userConfig.keySet()) { 1741 for (String param : params) { 1742 Pattern pattern = Pattern.compile(param, Pattern.CASE_INSENSITIVE); 1743 if (pattern.matcher((String) key).find()) { 1744 keys.add((String) key); 1745 } 1746 } 1747 } 1748 if (keys.isEmpty()) { 1749 errorValue = EXIT_CODE_NOT_CONFIGURED; 1750 } 1751 } else { 1752 keys = Arrays.asList(params); 1753 } 1754 1755 Crypto crypto = userConfig.getCrypto(); 1756 boolean keyChecked = false; // Secret key is asked only once 1757 boolean raw = true; 1758 StringBuilder sb = new StringBuilder(); 1759 final String newLine = System.getProperty("line.separator"); 1760 for (String key : keys) { 1761 String value = userConfig.getProperty(key, raw); 1762 if (value == null) { 1763 errorValue = EXIT_CODE_NOT_CONFIGURED; 1764 sb.append(OUTPUT_UNSET_VALUE + newLine); 1765 } else { 1766 if (raw && !keyChecked && Crypto.isEncrypted(value)) { 1767 keyChecked = true; 1768 boolean validKey; 1769 Console console = System.console(); 1770 if (console != null) { 1771 validKey = crypto.verifyKey(console.readPassword("Please enter the secret key: ")); 1772 } else { // try reading from stdin 1773 try { 1774 validKey = crypto.verifyKey(IOUtils.toByteArray(System.in)); 1775 } catch (IOException e) { 1776 log.debug(e, e); 1777 errorValue = EXIT_CODE_ERROR; 1778 return; 1779 } 1780 } 1781 if (validKey) { 1782 raw = false; 1783 value = new String(crypto.decrypt(value)); 1784 } else { 1785 errorValue = EXIT_CODE_INVALID; 1786 } 1787 } 1788 if (isRegexp) { 1789 sb.append(key + "="); 1790 } 1791 sb.append(value + newLine); 1792 } 1793 } 1794 System.out.print(sb.toString()); 1795 } 1796 1797 /** 1798 * @throws IOException 1799 * @throws GeneralSecurityException 1800 * @since 7.4 1801 */ 1802 protected void setConfigProperties() throws ConfigurationException, IOException, GeneralSecurityException { 1803 Crypto crypto = configurationGenerator.getCrypto(); 1804 boolean doEncrypt = cmdLine.hasOption(OPTION_ENCRYPT); 1805 String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null); 1806 Map<String, String> changedParameters = new HashMap<>(); 1807 for (Iterator<String> iterator = Arrays.asList(params).iterator(); iterator.hasNext();) { 1808 String key = iterator.next(); 1809 String value; 1810 if (iterator.hasNext()) { 1811 value = iterator.next(); 1812 if (doEncrypt) { 1813 value = crypto.encrypt(algorithm, value.getBytes()); 1814 } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) { 1815 value = Base64.encodeBase64String(value.getBytes()); 1816 } 1817 } else { 1818 Console console = System.console(); 1819 if (console != null) { 1820 final String fmt = "Please enter the value for %s: "; 1821 if (doEncrypt) { 1822 value = crypto.encrypt(algorithm, Crypto.getBytes(console.readPassword(fmt, key))); 1823 } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) { 1824 value = Base64.encodeBase64String(Crypto.getBytes(console.readPassword(fmt, key))); 1825 } else { 1826 value = console.readLine(fmt, key); 1827 } 1828 } else { // try reading from stdin 1829 try { 1830 if (doEncrypt) { 1831 value = crypto.encrypt(algorithm, IOUtils.toByteArray(System.in)); 1832 } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) { 1833 value = Base64.encodeBase64String(IOUtils.toByteArray(System.in)); 1834 } else { 1835 value = IOUtils.toString(System.in); 1836 } 1837 } catch (IOException e) { 1838 log.debug(e, e); 1839 errorValue = EXIT_CODE_ERROR; 1840 return; 1841 } 1842 } 1843 } 1844 changedParameters.put(key, value); 1845 } 1846 String template = cmdLine.getOptionValue(OPTION_SET); 1847 Map<String, String> oldValues; 1848 if (template == null) { 1849 oldValues = configurationGenerator.setProperties(changedParameters); 1850 } else { 1851 oldValues = configurationGenerator.setProperties(template, changedParameters); 1852 } 1853 log.debug("Old values: " + oldValues); 1854 } 1855 1856 /** 1857 * Since 5.5 1858 */ 1859 protected boolean pack() { 1860 try { 1861 configurationGenerator.setProperty(PARAM_UPDATECENTER_DISABLED, "true"); 1862 List<String> startCommand = new ArrayList<>(); 1863 startCommand.add(getJavaExecutable().getPath()); 1864 startCommand.addAll(getJavaOptsProperty(Function.identity())); 1865 startCommand.add("-cp"); 1866 String classpath = getClassPath(); 1867 classpath = addToClassPath(classpath, "bin" + File.separator + "nuxeo-launcher.jar"); 1868 classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getServerLibDir()); 1869 classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getNuxeoLibDir()); 1870 classpath = getClassPath(classpath, new File(configurationGenerator.getRuntimeHome(), "bundles")); 1871 startCommand.add(classpath); 1872 startCommand.addAll(getNuxeoProperties()); 1873 if (configurationGenerator.isTomcat) { 1874 startCommand.add(PACK_TOMCAT_CLASS); 1875 } else { 1876 errorValue = EXIT_CODE_ERROR; 1877 return false; 1878 } 1879 startCommand.add(configurationGenerator.getRuntimeHome().getPath()); 1880 for (String param : params) { 1881 startCommand.add(param); 1882 } 1883 ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand)); 1884 pb.directory(configurationGenerator.getNuxeoHome()); 1885 log.debug("Pack command: " + pb.command()); 1886 Process process = pb.start(); 1887 ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(process, true); 1888 Thread.sleep(100); 1889 process.waitFor(); 1890 waitForProcessStreams(sgArray); 1891 } catch (IOException | InterruptedException e) { 1892 errorValue = EXIT_CODE_ERROR; 1893 log.error("Could not start process", e); 1894 } catch (ConfigurationException e) { 1895 errorValue = EXIT_CODE_ERROR; 1896 log.error(e); 1897 } 1898 return errorValue == EXIT_CODE_OK; 1899 } 1900 1901 protected boolean startWizard() throws PackageException { 1902 if (!configurationGenerator.getServerConfigurator().isWizardAvailable()) { 1903 log.error("Sorry, the wizard is not available within that server."); 1904 return false; 1905 } 1906 if (isRunning()) { 1907 log.error("Server already running. " + "Please stop it before calling \"wizard\" command " 1908 + "or use the Admin Center instead of the wizard."); 1909 return false; 1910 } 1911 if (reloadConfiguration) { 1912 configurationGenerator = new ConfigurationGenerator(quiet, debug); 1913 configurationGenerator.init(); 1914 reloadConfiguration = false; 1915 } 1916 configurationGenerator.getUserConfig().setProperty(ConfigurationGenerator.PARAM_WIZARD_DONE, "false"); 1917 return doStart(); 1918 } 1919 1920 /** 1921 * @throws PackageException 1922 * @see #doStartAndWait(boolean) 1923 */ 1924 public boolean doStartAndWait() throws PackageException { 1925 boolean started = doStartAndWait(false); 1926 if (started && !quiet) { 1927 log.info("Go to " + getURL()); 1928 } 1929 return started; 1930 } 1931 1932 /** 1933 * @see #stop(boolean) 1934 */ 1935 public void stop() { 1936 stop(false); 1937 } 1938 1939 /** 1940 * Call {@link #doStart(boolean)} with false as parameter. 1941 * 1942 * @see #doStart(boolean) 1943 * @return true if the server started successfully 1944 * @throws PackageException 1945 */ 1946 public boolean doStart() throws PackageException { 1947 boolean started = doStart(false); 1948 if (started && !quiet) { 1949 log.info("Go to " + getURL()); 1950 } 1951 return started; 1952 } 1953 1954 /** 1955 * Whereas {@link #doStart()} considers the server as started when the 1956 * process is running, {@link #doStartAndWait()} waits for effective start 1957 * by watching the logs 1958 * 1959 * @param logProcessOutput 1960 * Must process output stream must be logged or not. 1961 * @return true if the server started successfully 1962 * @throws PackageException 1963 */ 1964 public boolean doStartAndWait(boolean logProcessOutput) throws PackageException { 1965 boolean commandSucceeded = false; 1966 if (doStart(logProcessOutput)) { 1967 addShutdownHook(); 1968 try { 1969 if (configurationGenerator.isWizardRequired() || waitForEffectiveStart()) { 1970 commandSucceeded = true; 1971 } 1972 removeShutdownHook(); 1973 } catch (InterruptedException e) { 1974 // do nothing 1975 } 1976 } 1977 return commandSucceeded; 1978 } 1979 1980 protected void removeShutdownHook() { 1981 try { 1982 Runtime.getRuntime().removeShutdownHook(shutdownHook); 1983 log.debug("Removed shutdown hook"); 1984 } catch (IllegalStateException e) { 1985 // the virtual machine is already in the process of shutting down 1986 } 1987 } 1988 1989 /** 1990 * @return true if Nuxeo is ready 1991 * @throws InterruptedException 1992 */ 1993 protected boolean waitForEffectiveStart() throws InterruptedException { 1994 long startTime = new Date().getTime(); 1995 int startMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(START_MAX_WAIT_PARAM, 1996 getDefaultMaxWait())); 1997 log.debug("Will wait for effective start during " + startMaxWait + " seconds."); 1998 final StringBuilder startSummary = new StringBuilder(); 1999 final String newLine = System.getProperty("line.separator"); 2000 boolean isReady = false; 2001 long deltaTime = 0; 2002 // Wait for status servlet ready 2003 do { 2004 try { 2005 isReady = statusServletClient.init(); 2006 } catch (SocketTimeoutException e) { 2007 if (!quiet) { 2008 System.out.print("."); 2009 } 2010 } 2011 deltaTime = (new Date().getTime() - startTime) / 1000; 2012 } while (!isReady && deltaTime < startMaxWait && isRunning()); 2013 isReady = false; 2014 // Wait for effective start reported from status servlet 2015 do { 2016 isReady = isStarted(); 2017 if (!isReady) { 2018 if (!quiet) { 2019 System.out.print("."); 2020 } 2021 Thread.sleep(1000); 2022 } 2023 deltaTime = (new Date().getTime() - startTime) / 1000; 2024 } while (!isReady && deltaTime < startMaxWait && isRunning()); 2025 if (isReady) { 2026 startSummary.append(newLine + getStartupSummary()); 2027 long duration = (new Date().getTime() - startTime) / 1000; 2028 startSummary.append("Started in " 2029 + String.format("%dmin%02ds", new Long(duration / 60), new Long(duration % 60))); 2030 if (wasStartupFine()) { 2031 if (!quiet) { 2032 System.out.println(startSummary); 2033 } 2034 } else { 2035 System.err.println(startSummary); 2036 if (strict) { 2037 errorValue = EXIT_CODE_ERROR; 2038 log.error("Shutting down because of unstarted component in strict mode..."); 2039 stop(); 2040 return false; 2041 } 2042 } 2043 return true; 2044 } else if (deltaTime >= startMaxWait) { 2045 if (!quiet) { 2046 System.out.println(); 2047 } 2048 log.error("Starting process is taking too long - giving up."); 2049 } 2050 errorValue = EXIT_CODE_ERROR; 2051 return false; 2052 } 2053 2054 /** 2055 * Must be called after {@link #getStartupSummary()} 2056 * 2057 * @since 5.5 2058 * @return last detected status of running Nuxeo server 2059 */ 2060 public boolean wasStartupFine() { 2061 return statusServletClient.isStartupFine(); 2062 } 2063 2064 /** 2065 * @since 5.5 2066 * @return Nuxeo startup summary 2067 */ 2068 public String getStartupSummary() { 2069 try { 2070 return statusServletClient.getStartupSummary(); 2071 } catch (SocketTimeoutException e) { 2072 log.warn("Failed to contact Nuxeo for getting startup summary", e); 2073 return ""; 2074 } 2075 } 2076 2077 /** 2078 * Starts the server in background. 2079 * 2080 * @return true if server successfully started 2081 * @throws PackageException 2082 */ 2083 public boolean doStart(boolean logProcessOutput) throws PackageException { 2084 errorValue = EXIT_CODE_OK; 2085 boolean serverStarted = false; 2086 try { 2087 if (reloadConfiguration) { 2088 configurationGenerator = new ConfigurationGenerator(quiet, debug); 2089 configurationGenerator.init(); 2090 } else { 2091 // Ensure reload on next start 2092 reloadConfiguration = true; 2093 } 2094 configure(); 2095 configurationGenerator.verifyInstallation(); 2096 2097 if (configurationGenerator.isWizardRequired()) { 2098 if (!configurationGenerator.isForceGeneration()) { 2099 log.error("Cannot start setup wizard with " + ConfigurationGenerator.PARAM_FORCE_GENERATION 2100 + "=false. Either set it to true or once, either set " 2101 + ConfigurationGenerator.PARAM_WIZARD_DONE + "=true to skip the wizard."); 2102 errorValue = EXIT_CODE_NOT_CONFIGURED; 2103 return false; 2104 } 2105 String paramsStr = ""; 2106 for (String param : params) { 2107 paramsStr += " " + param; 2108 } 2109 System.setProperty(ConfigurationGenerator.PARAM_WIZARD_RESTART_PARAMS, paramsStr); 2110 configurationGenerator.prepareWizardStart(); 2111 } else { 2112 configurationGenerator.cleanupPostWizard(); 2113 } 2114 2115 log.debug("Check if install in progress..."); 2116 if (configurationGenerator.isInstallInProgress()) { 2117 if (!getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true, 2118 ignoreMissing)) { 2119 errorValue = EXIT_CODE_ERROR; 2120 log.error(String.format( 2121 "Start interrupted due to failure on pending actions. You can resume with a new start;" 2122 + " or you can restore the file '%s', optionally using the '--%s' option.", 2123 configurationGenerator.getInstallFile().getName(), OPTION_IGNORE_MISSING)); 2124 return false; 2125 } 2126 2127 // configuration will be reloaded, keep wizard value 2128 System.setProperty( 2129 ConfigurationGenerator.PARAM_WIZARD_DONE, 2130 configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_WIZARD_DONE, 2131 "true")); 2132 return doStart(logProcessOutput); 2133 } 2134 2135 start(logProcessOutput); 2136 serverStarted = isRunning(); 2137 if (pid != null) { 2138 File pidFile = new File(configurationGenerator.getPidDir(), "nuxeo.pid"); 2139 try (FileWriter writer = new FileWriter(pidFile)) { 2140 writer.write(pid); 2141 } 2142 } 2143 } catch (ConfigurationException e) { 2144 errorValue = EXIT_CODE_NOT_CONFIGURED; 2145 log.error("Could not run configuration: " + e.getMessage()); 2146 log.debug(e, e); 2147 } catch (IOException e) { 2148 errorValue = EXIT_CODE_ERROR; 2149 log.error("Could not start process: " + e.getMessage()); 2150 log.debug(e, e); 2151 } catch (InterruptedException e) { 2152 errorValue = EXIT_CODE_ERROR; 2153 log.error("Could not start process: " + e.getMessage()); 2154 log.debug(e, e); 2155 } catch (IllegalStateException e) { 2156 if (strict) { 2157 // assume program is not configured because of http port binding 2158 // conflict 2159 errorValue = EXIT_CODE_NOT_CONFIGURED; 2160 } 2161 log.error(e.getMessage()); 2162 } 2163 return serverStarted; 2164 } 2165 2166 /** 2167 * @since 5.6 2168 */ 2169 protected void printXMLOutput() { 2170 try { 2171 JAXBContext jaxbContext = JAXBContext.newInstance(CommandSetInfo.class, CommandInfo.class, 2172 PackageInfo.class, MessageInfo.class); 2173 printXMLOutput(jaxbContext, cset); 2174 } catch (JAXBException e) { 2175 log.error("Output serialization failed: " + e.getMessage(), e); 2176 errorValue = EXIT_CODE_NOT_RUNNING; 2177 } 2178 } 2179 2180 /** 2181 * @since 5.6 2182 */ 2183 protected void printXMLOutput(JAXBContext jaxbContext, Object objectToOutput) { 2184 try { 2185 printXMLOutput(jaxbContext, objectToOutput, System.out); 2186 } catch (JAXBException | XMLStreamException | FactoryConfigurationError e) { 2187 log.error("Output serialization failed: " + e.getMessage(), e); 2188 errorValue = EXIT_CODE_NOT_RUNNING; 2189 } 2190 } 2191 2192 /** 2193 * @since 8.3 2194 */ 2195 protected void printXMLOutput(JAXBContext context, Object object, OutputStream out) 2196 throws XMLStreamException, FactoryConfigurationError, JAXBException { 2197 XMLStreamWriter writer = jsonOutput ? jsonWriter(context, out) : xmlWriter(context, out); 2198 Marshaller marshaller = context.createMarshaller(); 2199 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 2200 marshaller.marshal(object, writer); 2201 } 2202 2203 protected XMLStreamWriter jsonWriter(JAXBContext context, OutputStream out) { 2204 JSONConfiguration config = JSONConfiguration.mapped().rootUnwrapping(true).attributeAsElement("key", "value").build(); 2205 config = JSONConfiguration.createJSONConfigurationWithFormatted(config, true); 2206 return JsonXmlStreamWriter.createWriter(new OutputStreamWriter(out), config, ""); 2207 } 2208 2209 protected XMLStreamWriter xmlWriter(JAXBContext context, OutputStream out) throws XMLStreamException, FactoryConfigurationError { 2210 return XMLOutputFactory.newInstance().createXMLStreamWriter(out); 2211 } 2212 2213 /** 2214 * Stop stream gobblers contained in the given ArrayList 2215 * 2216 * @since 5.5 2217 * @see #logProcessStreams(Process, boolean) 2218 */ 2219 public void waitForProcessStreams(ArrayList<ThreadedStreamGobbler> sgArray) { 2220 for (ThreadedStreamGobbler streamGobbler : sgArray) { 2221 try { 2222 streamGobbler.join(STREAM_MAX_WAIT); 2223 } catch (InterruptedException e) { 2224 streamGobbler.interrupt(); 2225 } 2226 } 2227 } 2228 2229 /** 2230 * @since 5.5 2231 * @param classpath 2232 * @param baseDir 2233 * @return classpath with all jar files in baseDir 2234 * @throws IOException 2235 */ 2236 protected String getClassPath(String classpath, File baseDir) throws IOException { 2237 File[] files = getFilename(baseDir, ".*"); 2238 for (File file : files) { 2239 classpath += System.getProperty("path.separator") + file.getPath(); 2240 } 2241 return classpath; 2242 } 2243 2244 /** 2245 * @since 5.5 2246 * @param baseDir 2247 * @param filePattern 2248 * @return filename matching filePattern in baseDir 2249 */ 2250 protected File[] getFilename(File baseDir, final String filePattern) { 2251 File[] files = baseDir.listFiles(new FilenameFilter() { 2252 @Override 2253 public boolean accept(File basedir, String filename) { 2254 return filename.matches(filePattern + "(-[0-9].*)?\\.jar"); 2255 } 2256 }); 2257 return files; 2258 } 2259 2260 protected class ShutdownThread extends Thread { 2261 2262 private NuxeoLauncher launcher; 2263 2264 public ShutdownThread(NuxeoLauncher launcher) { 2265 super(); 2266 this.launcher = launcher; 2267 } 2268 2269 @Override 2270 public void run() { 2271 log.debug("Shutting down..."); 2272 if (launcher.isRunning()) { 2273 launcher.stop(); 2274 } 2275 log.debug("Shutdown complete."); 2276 } 2277 } 2278 2279 protected void addShutdownHook() { 2280 log.debug("Add shutdown hook"); 2281 shutdownHook = new ShutdownThread(this); 2282 Runtime.getRuntime().addShutdownHook(shutdownHook); 2283 } 2284 2285 /** 2286 * Stops the server. Will try to call specific class for a clean stop, 2287 * retry, waiting between each try, then kill the process if still running. 2288 */ 2289 public void stop(boolean logProcessOutput) { 2290 long startTime = new Date().getTime(); 2291 long deltaTime; 2292 try { 2293 if (!isRunning()) { 2294 log.warn("Server is not running."); 2295 return; 2296 } 2297 if (!quiet) { 2298 System.out.print("\nStopping server..."); 2299 } 2300 int nbTry = 0; 2301 boolean retry = false; 2302 int stopMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(STOP_MAX_WAIT_PARAM, 2303 STOP_MAX_WAIT_DEFAULT)); 2304 do { 2305 List<String> stopCommand = new ArrayList<>(); 2306 stopCommand.add(getJavaExecutable().getPath()); 2307 stopCommand.add("-cp"); 2308 stopCommand.add(getShutdownClassPath()); 2309 stopCommand.addAll(getNuxeoProperties()); 2310 stopCommand.addAll(getServerProperties()); 2311 setServerStopCommand(stopCommand); 2312 for (String param : params) { 2313 stopCommand.add(param); 2314 } 2315 ProcessBuilder pb = new ProcessBuilder(getOSCommand(stopCommand)); 2316 pb.directory(configurationGenerator.getNuxeoHome()); 2317 // pb = pb.redirectErrorStream(true); 2318 log.debug("Server command: " + pb.command()); 2319 try { 2320 Process stopProcess = pb.start(); 2321 ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(stopProcess, logProcessOutput); 2322 stopProcess.waitFor(); 2323 waitForProcessStreams(sgArray); 2324 boolean wait = true; 2325 while (wait) { 2326 try { 2327 if (stopProcess.exitValue() == 0) { 2328 // Successful call for server stop 2329 retry = false; 2330 } else { 2331 // Failed to call for server stop 2332 retry = ++nbTry < STOP_NB_TRY; 2333 if (!quiet) { 2334 System.out.print("."); 2335 } 2336 Thread.sleep(STOP_SECONDS_BEFORE_NEXT_TRY * 1000); 2337 } 2338 wait = false; 2339 } catch (IllegalThreadStateException e) { 2340 // Stop call is still running 2341 wait = true; 2342 if (!quiet) { 2343 System.out.print("."); 2344 } 2345 Thread.sleep(1000); 2346 } 2347 } 2348 // Exit if there's no way to check for server stop 2349 if (!processManager.canFindPid()) { 2350 log.warn("Can't check server status on your OS."); 2351 return; 2352 } 2353 // Wait a few seconds for effective stop 2354 deltaTime = 0; 2355 do { 2356 if (!quiet) { 2357 System.out.print("."); 2358 } 2359 Thread.sleep(1000); 2360 deltaTime = (new Date().getTime() - startTime) / 1000; 2361 } while (!retry && getPid() != null && deltaTime < stopMaxWait); 2362 } catch (InterruptedException e) { 2363 log.error(e); 2364 } 2365 } while (retry); 2366 if (getPid() == null) { 2367 log.warn("Server stopped."); 2368 } else { 2369 log.info("No answer from server, try to kill process " + pid + "..."); 2370 processManager.kill(nuxeoProcess, pid); 2371 if (getPid() == null) { 2372 log.warn("Server forcibly stopped."); 2373 } 2374 } 2375 } catch (IOException e) { 2376 log.error("Could not manage process!", e); 2377 } 2378 } 2379 2380 protected abstract void setServerStopCommand(List<String> command); 2381 2382 private String getPid() throws IOException { 2383 pid = processManager.findPid(processRegex); 2384 log.debug("regexp: " + processRegex + " pid:" + pid); 2385 return pid; 2386 } 2387 2388 /** 2389 * Configure the server after checking installation 2390 * 2391 * @throws ConfigurationException 2392 * If an installation error is detected or if configuration 2393 * fails 2394 */ 2395 public void configure() throws ConfigurationException { 2396 try { 2397 checkNoRunningServer(); 2398 configurationGenerator.checkJavaVersion(); 2399 configurationGenerator.run(); 2400 overrideJavaTmpDir = Boolean.parseBoolean(configurationGenerator.getUserConfig().getProperty( 2401 OVERRIDE_JAVA_TMPDIR_PARAM, "true")); 2402 } catch (ConfigurationException e) { 2403 errorValue = EXIT_CODE_NOT_CONFIGURED; 2404 throw e; 2405 } 2406 } 2407 2408 /** 2409 * @return Default max wait depending on server (ie JBoss takes much more 2410 * time than Tomcat) 2411 */ 2412 private String getDefaultMaxWait() { 2413 return START_MAX_WAIT_DEFAULT; 2414 } 2415 2416 /** 2417 * Return process status (running or not) as String, depending on OS 2418 * capability to manage processes. Set status value following 2419 * "http://refspecs.freestandards.org/LSB_4.1.0/LSB-Core-generic/LSB-Core- 2420 * generic/iniscrptact.html" 2421 * 2422 * @see #getStatus() 2423 */ 2424 public String status() { 2425 try { 2426 if (processManager instanceof PureJavaProcessManager) { 2427 status = STATUS_CODE_UNKNOWN; 2428 return "Can't check server status on your OS."; 2429 } 2430 if (getPid() == null) { 2431 status = STATUS_CODE_OFF; 2432 return "Server is not running."; 2433 } else { 2434 status = STATUS_CODE_ON; 2435 return "Server is running with process ID " + getPid() + "."; 2436 } 2437 } catch (IOException e) { 2438 status = STATUS_CODE_UNKNOWN; 2439 return "Could not check existing process (" + e.getMessage() + ")."; 2440 } 2441 } 2442 2443 /** 2444 * Last status value set by {@link #status()}. 2445 */ 2446 public int getStatus() { 2447 return status; 2448 } 2449 2450 /** 2451 * Last error value set by any method. Exit code values are following the 2452 * Linux Standard Base Core Specification 4.1. 2453 */ 2454 public int getErrorValue() { 2455 return errorValue; 2456 } 2457 2458 /** 2459 * @throws ParseException 2460 * @return a NuxeoLauncher instance specific to current server ( Tomcat or 2461 * Jetty). 2462 * @throws ConfigurationException 2463 * If server cannot be identified 2464 * @throws PackageException 2465 * @throws IOException 2466 * @since 5.5 2467 */ 2468 public static NuxeoLauncher createLauncher(String[] args) throws ConfigurationException, ParseException, IOException, PackageException { 2469 CommandLine cmdLine = parseOptions(args); 2470 ConfigurationGenerator cg = new ConfigurationGenerator(quiet, debug); 2471 if (cmdLine.hasOption(OPTION_HIDE_DEPRECATION)) { 2472 cg.hideDeprecationWarnings(true); 2473 } 2474 NuxeoLauncher launcher; 2475 if (cg.isJetty) { 2476 launcher = new NuxeoJettyLauncher(cg); 2477 } else if (cg.isTomcat) { 2478 launcher = new NuxeoTomcatLauncher(cg); 2479 } else { 2480 throw new ConfigurationException("Unknown server!"); 2481 } 2482 launcher.connectBroker = new ConnectBroker(launcher.configurationGenerator.getEnv()); 2483 launcher.setArgs(cmdLine); 2484 launcher.initConnectBroker(); 2485 return launcher; 2486 } 2487 2488 /** 2489 * Sets from program arguments the launcher command and additional 2490 * parameters. 2491 * 2492 * @param cmdLine 2493 * Program arguments; may be used by launcher implementation. 2494 * Must not be null or empty. 2495 * @throws ConfigurationException 2496 */ 2497 private void setArgs(CommandLine cmdLine) throws ConfigurationException { 2498 this.cmdLine = cmdLine; 2499 extractCommandAndParams(cmdLine.getArgs()); 2500 // Use GUI? 2501 if (cmdLine.hasOption(OPTION_GUI)) { 2502 useGui = Boolean.valueOf(ConnectBroker.parseAnswer(cmdLine.getOptionValue(OPTION_GUI))); 2503 log.debug("GUI: " + cmdLine.getOptionValue(OPTION_GUI) + " -> " + new Boolean(useGui).toString()); 2504 } else if (OPTION_GUI.equalsIgnoreCase(command)) { 2505 useGui = true; 2506 // Shift params and extract command if there is one 2507 extractCommandAndParams(params); 2508 } else { 2509 if (SystemUtils.IS_OS_WINDOWS) { 2510 useGui = true; 2511 log.debug("GUI: option not set - platform is Windows -> start GUI"); 2512 } else { 2513 useGui = false; 2514 log.debug("GUI: option not set - platform is not Windows -> do not start GUI"); 2515 } 2516 } 2517 // Output format 2518 if (cmdLine.hasOption(OPTION_XML)) { 2519 setXMLOutput(); 2520 } 2521 if (cmdLine.hasOption(OPTION_JSON)) { 2522 setJSONOutput(); 2523 } 2524 if (cmdLine.hasOption(OPTION_CLID)) { 2525 try { 2526 getConnectBroker().setCLID(cmdLine.getOptionValue(OPTION_CLID)); 2527 } catch (NoCLID | IOException | PackageException e) { 2528 throw new ConfigurationException(e); 2529 } 2530 } 2531 if (cmdLine.hasOption(OPTION_IGNORE_MISSING)) { 2532 ignoreMissing = true; 2533 } 2534 } 2535 2536 private void extractCommandAndParams(String[] args) { 2537 if (args.length > 0) { 2538 command = args[0]; 2539 log.debug("Launcher command: " + command); 2540 // Command parameters 2541 if (args.length > 1) { 2542 params = Arrays.copyOfRange(args, 1, args.length); 2543 if (log.isDebugEnabled()) { 2544 log.debug("Command parameters: " + ArrayUtils.toString(params)); 2545 } 2546 } else { 2547 params = new String[0]; 2548 } 2549 } else { 2550 command = null; 2551 } 2552 } 2553 2554 /** 2555 * Set launcher in quiet mode 2556 * 2557 * @since 5.5 2558 */ 2559 protected static void setQuiet() { 2560 quiet = true; 2561 Log4JHelper.setQuiet(Log4JHelper.CONSOLE_APPENDER_NAME); 2562 } 2563 2564 /** 2565 * @param categories 2566 * Root categories to switch DEBUG on. 2567 * @since 7.4 2568 */ 2569 protected static void setDebug(String[] categories, String defaultCategory) { 2570 debug = true; 2571 if (categories == null) { 2572 categories = new String[] { defaultCategory }; 2573 } 2574 Log4JHelper.setDebug(categories, true, true, new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" }); 2575 } 2576 2577 /** 2578 * @param categories 2579 * Root categories to switch DEBUG on. 2580 * @since 5.6 2581 */ 2582 protected static void setDebug(String categories) { 2583 setDebug(categories, true); 2584 } 2585 2586 /** 2587 * @param categories 2588 * Root categories to switch DEBUG on or off 2589 * @param activateDebug 2590 * Set DEBUG on or off. 2591 * @since 5.6 2592 */ 2593 protected static void setDebug(String categories, boolean activateDebug) { 2594 debug = activateDebug; 2595 Log4JHelper.setDebug(categories, activateDebug, true, 2596 new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" }); 2597 } 2598 2599 /** 2600 * @param activateDebug 2601 * if true, will activate the DEBUG logs 2602 * @since 5.5 2603 */ 2604 protected static void setDebug(boolean activateDebug) { 2605 setDebug("org.nuxeo", activateDebug); 2606 } 2607 2608 /** 2609 * @param isStrict 2610 * if {@code true}, set the launcher strict option 2611 * @since 7.4 2612 * @see #OPTION_STRICT_DESC 2613 */ 2614 protected static void setStrict(boolean isStrict) { 2615 strict = isStrict; 2616 } 2617 2618 protected void setXMLOutput() { 2619 xmlOutput = true; 2620 } 2621 2622 protected void setJSONOutput() { 2623 jsonOutput = true; 2624 setXMLOutput(); 2625 } 2626 2627 public static void printShortHelp() { 2628 System.out.println(); 2629 HelpFormatter help = new HelpFormatter(); 2630 help.setSyntaxPrefix("USAGE\n"); 2631 help.setOptionComparator(null); 2632 help.setWidth(1000); 2633 help.printHelp(OPTION_HELP_USAGE, "OPTIONS", options, null); 2634 System.out.println(OPTION_HELP_DESC_COMMANDS); 2635 } 2636 2637 public static void printLongHelp() { 2638 System.out.println(); 2639 HelpFormatter help = new HelpFormatter(); 2640 help.setSyntaxPrefix("USAGE\n"); 2641 help.setOptionComparator(null); 2642 help.setWidth(1000); 2643 help.printHelp(OPTION_HELP_USAGE, OPTION_HELP_HEADER, options, null); 2644 System.out.println(OPTION_HELP_DESC_ENV); 2645 System.out.println(OPTION_HELP_DESC_COMMANDS); 2646 System.out.println(OPTION_HELP_FOOTER); 2647 } 2648 2649 /** 2650 * Work best with current nuxeoProcess. If nuxeoProcess is null or has 2651 * exited, then will try to get process ID (so, result in that case depends 2652 * on OS capabilities). 2653 * 2654 * @return true if current process is running or if a running PID is found 2655 */ 2656 public boolean isRunning() { 2657 if (nuxeoProcess != null) { 2658 try { 2659 nuxeoProcess.exitValue(); 2660 // Previous process has exited 2661 nuxeoProcess = null; 2662 } catch (IllegalThreadStateException exception) { 2663 return true; 2664 } 2665 } 2666 try { 2667 return (getPid() != null); 2668 } catch (IOException e) { 2669 log.error(e); 2670 return false; 2671 } 2672 } 2673 2674 /** 2675 * Provides this instance info 2676 * 2677 * @since 8.3 2678 * 2679 */ 2680 public InstanceInfo getInfo() { 2681 return info; 2682 } 2683 2684 /** 2685 * @since 5.5 2686 * @return true if Nuxeo finished starting 2687 */ 2688 public boolean isStarted() { 2689 boolean isStarted; 2690 if (configurationGenerator.isWizardRequired()) { 2691 isStarted = isRunning(); 2692 } else { 2693 try { 2694 isStarted = isRunning() && statusServletClient.isStarted(); 2695 } catch (SocketTimeoutException e) { 2696 isStarted = false; 2697 } 2698 } 2699 return isStarted; 2700 } 2701 2702 /** 2703 * @return Server log file 2704 */ 2705 public File getLogFile() { 2706 return new File(configurationGenerator.getLogDir(), "server.log"); 2707 } 2708 2709 /** 2710 * @return Server URL 2711 */ 2712 public String getURL() { 2713 return configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_NUXEO_URL); 2714 } 2715 2716 protected void initConnectBroker() throws IOException, PackageException { 2717 if (cmdLine.hasOption(OPTION_ACCEPT)) { 2718 connectBroker.setAccept(cmdLine.getOptionValue(OPTION_ACCEPT)); 2719 } 2720 if (cmdLine.hasOption(OPTION_RELAX)) { 2721 connectBroker.setRelax(cmdLine.getOptionValue(OPTION_RELAX)); 2722 } 2723 if (cmdLine.hasOption(OPTION_SNAPSHOT)) { 2724 connectBroker.setAllowSNAPSHOT(true); 2725 } 2726 List<CommandInfo> csetCommands = cset.commands; 2727 cset = connectBroker.getCommandSet(); 2728 cset.commands.addAll(0, csetCommands); 2729 try { 2730 clid = connectBroker.getCLID(); 2731 } catch (NoCLID cause) { 2732 ; 2733 } 2734 info = configurationGenerator.getServerConfigurator().getInfo(clid, connectBroker.getPkgList()); 2735 if (new Version(info.distribution.version).isSnapshot()) { 2736 connectBroker.setAllowSNAPSHOT(true); 2737 } 2738 } 2739 2740 protected ConnectBroker getConnectBroker() throws IOException, PackageException { 2741 return connectBroker; 2742 } 2743 2744 protected ConnectRegistrationBroker getConnectRegistrationBroker() throws IOException, PackageException { 2745 if (connectRegistrationBroker == null) { 2746 getConnectBroker(); // Ensure ConnectBroker is instantiated too. 2747 connectRegistrationBroker = new ConnectRegistrationBroker(); 2748 } 2749 return connectRegistrationBroker; 2750 } 2751 2752 /** 2753 * List all local packages. 2754 * 2755 * @throws IOException 2756 * @throws PackageException 2757 */ 2758 protected void pkgList() throws IOException, PackageException { 2759 getConnectBroker().listPending(configurationGenerator.getInstallFile()); 2760 getConnectBroker().pkgList(); 2761 } 2762 2763 /** 2764 * List all packages including remote ones. 2765 * 2766 * @since 5.6 2767 * @throws IOException 2768 * @throws PackageException 2769 */ 2770 protected void pkgListAll() throws IOException, PackageException { 2771 getConnectBroker().listPending(configurationGenerator.getInstallFile()); 2772 getConnectBroker().pkgListAll(); 2773 } 2774 2775 protected boolean pkgAdd(String[] pkgNames) throws IOException, PackageException { 2776 boolean cmdOK = getConnectBroker().pkgAdd(Arrays.asList(pkgNames), ignoreMissing); 2777 if (!cmdOK) { 2778 errorValue = EXIT_CODE_ERROR; 2779 } 2780 return cmdOK; 2781 } 2782 2783 protected boolean pkgInstall(String[] pkgIDs) throws IOException, PackageException { 2784 boolean cmdOK = true; 2785 if (configurationGenerator.isInstallInProgress()) { 2786 cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, 2787 !cmdLine.hasOption(OPTION_NODEPS), ignoreMissing); 2788 } 2789 cmdOK = cmdOK && getConnectBroker().pkgInstall(Arrays.asList(pkgIDs), ignoreMissing); 2790 if (!cmdOK) { 2791 errorValue = EXIT_CODE_ERROR; 2792 } 2793 return cmdOK; 2794 } 2795 2796 protected boolean pkgUninstall(String[] pkgIDs) throws IOException, PackageException { 2797 boolean cmdOK = getConnectBroker().pkgUninstall(Arrays.asList(pkgIDs)); 2798 if (!cmdOK) { 2799 errorValue = EXIT_CODE_ERROR; 2800 } 2801 return cmdOK; 2802 } 2803 2804 protected boolean pkgRemove(String[] pkgIDs) throws IOException, PackageException { 2805 boolean cmdOK = getConnectBroker().pkgRemove(Arrays.asList(pkgIDs)); 2806 if (!cmdOK) { 2807 errorValue = EXIT_CODE_ERROR; 2808 } 2809 return cmdOK; 2810 } 2811 2812 protected boolean pkgReset() throws IOException, PackageException { 2813 boolean cmdOK = getConnectBroker().pkgReset(); 2814 if (!cmdOK) { 2815 errorValue = EXIT_CODE_ERROR; 2816 } 2817 return cmdOK; 2818 } 2819 2820 /** 2821 * @since 5.6 2822 */ 2823 protected void printInstanceXMLOutput(InstanceInfo instance) { 2824 try { 2825 printInstanceXMLOutput(instance, System.out); 2826 } catch (JAXBException | XMLStreamException | FactoryConfigurationError e) { 2827 log.error("Output serialization failed: " + e.getMessage()); 2828 log.debug(e, e); 2829 errorValue = EXIT_CODE_NOT_RUNNING; 2830 } 2831 } 2832 2833 protected void printInstanceXMLOutput(InstanceInfo instance, OutputStream out) 2834 throws JAXBException, XMLStreamException, FactoryConfigurationError { 2835 JAXBContext jaxbContext = JAXBContext.newInstance(InstanceInfo.class, DistributionInfo.class, 2836 PackageInfo.class, ConfigurationInfo.class, KeyValueInfo.class); 2837 printXMLOutput(jaxbContext, instance, out); 2838 } 2839 2840 /** 2841 * @throws PackageException 2842 * @throws IOException 2843 * @throws ConfigurationException 2844 * @since 5.6 2845 */ 2846 protected void showConfig() throws IOException, PackageException, ConfigurationException { 2847 log.info("***** Nuxeo instance configuration *****"); 2848 log.info("NUXEO_CONF: " + info.NUXEO_CONF); 2849 log.info("NUXEO_HOME: " + info.NUXEO_HOME); 2850 if (info.clid != null) { 2851 log.info("Instance CLID: " + info.clid); 2852 } 2853 // distribution.properties 2854 log.info("** Distribution"); 2855 log.info("- name: " + info.distribution.name); 2856 log.info("- server: " + info.distribution.server); 2857 log.info("- version: " + info.distribution.version); 2858 log.info("- date: " + info.distribution.date); 2859 log.info("- packaging: " + info.distribution.packaging); 2860 // packages 2861 log.info("** Packages:"); 2862 for (PackageInfo pkg : info.packages) { 2863 log.info(String.format("- %s (version: %s - id: %s - state: %s)", pkg.name, pkg.version, 2864 pkg.id, pkg.state.getLabel())); 2865 } 2866 // nuxeo.conf 2867 log.info("** Templates:"); 2868 log.info("Database template: " + info.config.dbtemplate); 2869 for (String template : info.config.pkgtemplates) { 2870 log.info("Package template: " + template); 2871 } 2872 for (String template : info.config.usertemplates) { 2873 log.info("User template: " + template); 2874 } 2875 for (String template : info.config.basetemplates) { 2876 log.info("Base template: " + template); 2877 } 2878 log.info("** Settings from nuxeo.conf:"); 2879 for (KeyValueInfo keyval : info.config.keyvals) { 2880 log.info(String.format("%s=%s", keyval.key, keyval.value)); 2881 } 2882 log.info("****************************************"); 2883 if (xmlOutput) { 2884 printInstanceXMLOutput(info); 2885 } 2886 } 2887 2888 /** 2889 * @since 5.6 2890 * @param pkgsToAdd 2891 * @param pkgsToInstall 2892 * @param pkgsToUninstall 2893 * @param pkgsToRemove 2894 * @return true if request execution was fine 2895 * @throws IOException 2896 * @throws PackageException 2897 */ 2898 protected boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall, 2899 List<String> pkgsToRemove) throws IOException, PackageException { 2900 boolean cmdOK = true; 2901 if (configurationGenerator.isInstallInProgress()) { 2902 cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true, 2903 ignoreMissing); 2904 } 2905 cmdOK = cmdOK 2906 && getConnectBroker().pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove, true, 2907 ignoreMissing); 2908 if (!cmdOK) { 2909 errorValue = EXIT_CODE_ERROR; 2910 } 2911 return cmdOK; 2912 } 2913 2914 /** 2915 * Update the cached list of remote packages 2916 * 2917 * @since 5.6 2918 * @return true 2919 * @throws IOException 2920 * @throws PackageException 2921 */ 2922 protected boolean pkgRefreshCache() throws IOException, PackageException { 2923 getConnectBroker().refreshCache(); 2924 return true; 2925 } 2926 2927 /** 2928 * Add packages from the distribution to the local cache 2929 * 2930 * @throws PackageException 2931 * @throws IOException 2932 * @since 5.6 2933 */ 2934 protected boolean pkgInit() throws IOException, PackageException { 2935 return getConnectBroker().addDistributionPackages(); 2936 } 2937 2938 /** 2939 * Uninstall and remove all packages from the local cache 2940 * 2941 * @return {@code true} if command succeed 2942 * @throws PackageException 2943 * @throws IOException 2944 * @since 5.6 2945 */ 2946 protected boolean pkgPurge() throws PackageException, IOException { 2947 return getConnectBroker().pkgPurge(); 2948 } 2949 2950 /** 2951 * Install the hotfixes available for the instance 2952 * 2953 * @return {@code true} if command succeed 2954 * @throws PackageException 2955 * @throws IOException 2956 * @since 5.6 2957 */ 2958 protected boolean pkgHotfix() throws IOException, PackageException { 2959 return getConnectBroker().pkgHotfix(); 2960 } 2961 2962 /** 2963 * Upgrade the Nuxeo Packages (addons) available for the instance 2964 * 2965 * @return {@code true} if command succeed 2966 * @throws PackageException 2967 * @throws IOException 2968 * @since 5.6 2969 */ 2970 protected boolean pkgUpgrade() throws IOException, PackageException { 2971 return getConnectBroker().pkgUpgrade(); 2972 } 2973 2974 /** 2975 * Combined install/uninstall request 2976 * 2977 * @param request 2978 * Space separated list of package names or IDs prefixed with + 2979 * (install) or - (uninstall) 2980 * @throws IOException 2981 * @throws PackageException 2982 * @since 5.6 2983 */ 2984 protected boolean pkgCompoundRequest(List<String> request) throws IOException, PackageException { 2985 List<String> add = new ArrayList<>(); 2986 List<String> install = new ArrayList<>(); 2987 List<String> uninstall = new ArrayList<>(); 2988 for (String param : request) { 2989 for (String subparam : param.split("[ ,]")) { 2990 if (subparam.charAt(0) == '-') { 2991 uninstall.add(subparam.substring(1)); 2992 } else if (subparam.charAt(0) == '+') { 2993 install.add(subparam.substring(1)); 2994 } else { 2995 add.add(subparam); 2996 } 2997 } 2998 } 2999 return pkgRequest(add, install, uninstall, null); 3000 } 3001 3002 protected boolean pkgSetRequest(List<String> request, boolean nodeps) throws IOException, PackageException { 3003 boolean cmdOK; 3004 if (nodeps) { 3005 cmdOK = getConnectBroker().pkgSet(request, ignoreMissing); 3006 } else { 3007 cmdOK = getConnectBroker().pkgRequest(null, request, null, null, false, ignoreMissing); 3008 } 3009 if (!cmdOK) { 3010 errorValue = EXIT_CODE_ERROR; 3011 } 3012 return cmdOK; 3013 } 3014 3015 /** 3016 * dpkg-like command which returns package location, version, dependencies, 3017 * conflicts, ... 3018 * 3019 * @param packages 3020 * List of packages identified by their ID, name or local 3021 * filename. 3022 * @return false if unable to show package information. 3023 * @throws PackageException 3024 * @throws IOException 3025 * @since 5.7 3026 */ 3027 protected boolean pkgShow(String[] packages) throws IOException, PackageException { 3028 boolean cmdOK = getConnectBroker().pkgShow(Arrays.asList(packages)); 3029 if (!cmdOK) { 3030 errorValue = EXIT_CODE_ERROR; 3031 } 3032 return cmdOK; 3033 } 3034 3035 protected boolean dumpConnectReport(OutputStream out, boolean prettyprint) { 3036 class MapBuilder<K, V> { 3037 final Map<K, V> store = new HashMap<>(); 3038 3039 MapBuilder<K, V> with(K key, V value) { 3040 store.put(key, value); 3041 return this; 3042 } 3043 3044 Map<K, V> build() { 3045 return store; 3046 } 3047 } 3048 try (JsonGenerator generator = 3049 Json.createGeneratorFactory(new MapBuilder<String, Object>().with(JsonGenerator.PRETTY_PRINTING, prettyprint).build()) 3050 .createGenerator(out)) { 3051 generator.writeStartObject(); 3052 ReportConnector.of().feed(generator); 3053 generator.writeEnd(); 3054 } catch (IOException | InterruptedException | ExecutionException cause) { 3055 log.error("Cannot dump connect report", cause); 3056 errorValue = EXIT_CODE_ERROR; 3057 return false; 3058 } 3059 return true; 3060 } 3061}