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