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