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