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