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