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