diff --git a/.project b/.project index fe8261c37b37..e375513d0954 100644 --- a/.project +++ b/.project @@ -14,4 +14,15 @@ org.eclipse.m2e.core.maven2Nature + + + 1790861360617 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java index e533ac739fe8..547f5f9c3bbc 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java @@ -21,6 +21,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; import java.text.DecimalFormat; import java.util.HashMap; import java.util.LinkedHashMap; @@ -81,6 +83,8 @@ public class Create extends InstallAbstract { public static final String BIN_ARTEMIS = "bin/" + ARTEMIS; public static final String ARTEMIS_SERVICE = "artemis-service"; public static final String BIN_ARTEMIS_SERVICE = "bin/" + ARTEMIS_SERVICE; + public static final String ARTEMIS_SERVICE_SYSTEMD = "artemis.service"; + public static final String ETC_ARTEMIS_SERVICE_SYSTEMD = "etc/" + ARTEMIS_SERVICE_SYSTEMD; public static final String ETC_ARTEMIS_PROFILE = "artemis.profile"; public static final String ETC_ARTEMIS_UTILITY_PROFILE = "artemis-utility.profile"; public static final String ETC_LOG4J2_PROPERTIES = "log4j2.properties"; @@ -340,6 +344,12 @@ public String[] getStaticNodes() { @Option(names = "--jdbc-lock-expiration", description = "Lock expiration (in milliseconds).") long jdbcLockExpiration = ActiveMQDefaultConfiguration.getDefaultJdbcLockExpirationMillis(); + @Option(names = "--enable-systemd-service", description = "Enable systemd service. Default is false.") + boolean enableSystemdService = false; + + @Option(names = "--systemd-service-name", description = "Name of the artemis systemd service. Default is 'artemis'.") + String systemdServiceName = "artemis"; + private boolean isAutoCreate() { if (autoCreate == null) { if (noAutoCreate != null) { @@ -573,6 +583,68 @@ private boolean isDisablePersistence() { return disablePersistence; } + public void setSystemdServiceInstall(boolean enableSystemdService) { + this.enableSystemdService = enableSystemdService; + } + + public void generateSystemdService(File etcFolder) throws Exception { + Map serviceFilters = new LinkedHashMap<>(); + + // set JAVA_ARGS_APPEND: Set console output level to OFF when runing as a service. Output to logs only. + serviceFilters.put("${java-args-append}", "JAVA_ARGS_APPEND=-Dartemis.console.level=OFF"); + // set the ARTEMIS_INSTANCE environment variable and the exec-start command to run the broker + serviceFilters.put("${environment}", "ARTEMIS_INSTANCE=" + path(directory)); + serviceFilters.put("${exec-start}", path(directory) + "/bin/artemis run"); + + write(ETC_ARTEMIS_SERVICE_SYSTEMD, new File(etcFolder, systemdServiceName + ".service"), serviceFilters, true, false); + } + + public void enableSystemdService() throws Exception { + + if (enableSystemdService) { + File systemdServiceFile = new File(directory, "etc/" + systemdServiceName + ".service"); + File targetServiceFile = new File("/etc/systemd/system/" + systemdServiceName + ".service"); + + try { // try enabling the service automatically + Files.copy(systemdServiceFile.toPath(), targetServiceFile.toPath()); + getActionContext().out.println("Executing systemctl daemon-reload && systemctl enable " + systemdServiceName + ".service ..."); + executeCommand("systemctl", "daemon-reload"); + executeCommand("systemctl", "enable", systemdServiceName + ".service"); + + getActionContext().out.println("Systemd unit file was generated and enabled at:"); + getActionContext().out.println(String.format(" /etc/systemd/system/%s.service", systemdServiceName)); + getActionContext().out.println(); + getActionContext().out.println("To start it, execute:"); + getActionContext().out.println(String.format(" systemctl start %s.service", systemdServiceName)); + } catch (FileAlreadyExistsException e) { + getActionContext().out.println("Service file already exists at " + targetServiceFile.getAbsolutePath() + "."); + } catch (Exception e) { + getActionContext().out.println("Unable to install service: " + e.getMessage()); + } + } else { // print out instructions to enable the service + getActionContext().out.println("Systemd unit file was generated at:"); + getActionContext().out.println(String.format(" \"%s\"", path(new File(directory, "etc/" + systemdServiceName + ".service")))); + getActionContext().out.println(); + getActionContext().out.println("To enable it, run this as root or with sudo privileges:"); + getActionContext().out.println(String.format(" cp \"%s\" /etc/systemd/system/%s.service", path(new File(directory, "etc/" + systemdServiceName + ".service")), systemdServiceName)); + getActionContext().out.println(String.format(" systemctl daemon-reload && systemctl enable %s.service", systemdServiceName)); + } + } + + private void executeCommand(String... command) throws Exception { + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectErrorStream(true); + Process process = pb.start(); + + String output = new String(process.getInputStream().readAllBytes()).trim(); + int exitCode = process.waitFor(); + + if (exitCode != 0) { + throw new RuntimeException(String.format("Command '%s' failed with exit code %d: %s", + String.join(" ", command), exitCode, output)); + } + } + @Override public Object execute(ActionContext context) throws Exception { this.checkDirectory(); @@ -796,7 +868,6 @@ public Object run(ActionContext context) throws Exception { boolean allowAnonymous = isAllowAnonymous(); - String retentionTag; if (retentionDays > 0) { if (retentionMaxBytes != null) { @@ -927,6 +998,11 @@ public Object run(ActionContext context) throws Exception { } writeEtc(ETC_JOLOKIA_ACCESS_XML, etcFolder, filters, false); + generateSystemdService(etcFolder); + if (IS_NIX) { + enableSystemdService(); + } + context.out.println(""); context.out.println("You can now start the broker by executing: "); context.out.println(""); diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java index 8d355f2e588f..725389ef0e65 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Upgrade.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -246,6 +247,19 @@ public Object run(ActionContext context) throws Exception { //we remove the unwanted wars after updating them above just in case we are upgrading from an older version where the format was different removeWars(context, bootstrapXml); + // copy the systemd service file if it doesn't exist yet + final File systemdService = new File(etcFolder, Create.ARTEMIS_SERVICE_SYSTEMD); + if (!systemdService.exists()) { + final String instancePath = directory.getCanonicalPath(); + final Map serviceFilters = new LinkedHashMap<>(); + serviceFilters.put("${java-args-append}", "JAVA_ARGS_APPEND=-Dartemis.console.level=OFF"); + serviceFilters.put("${environment}", "ARTEMIS_INSTANCE=" + instancePath); + serviceFilters.put("${exec-start}", instancePath + "/bin/artemis run"); + + context.out.println("Creating " + systemdService); + write(Create.ETC_ARTEMIS_SERVICE_SYSTEMD, systemdService, serviceFilters, true, false); + } + upgradeLogging(context, etcFolder, etcBkp); context.out.println(); diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.service b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.service new file mode 100644 index 000000000000..f2499245e4a4 --- /dev/null +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.service @@ -0,0 +1,44 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[Unit] +Description=Apache Artemis Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=exec +Environment=${environment} +Environment=${java-args-append} +ExecStart=${exec-start} +TimeoutStopSec=60s +SuccessExitStatus=143 +KillMode=mixed +LimitMEMLOCK=infinity +LimitNOFILE=524288 +LimitNPROC=31438 +LimitAS=infinity +LimitFSIZE=infinity +SendSIGKILL=no +NotifyAccess=all +Restart=always +RestartSec=10 +#User=artemis +#Group=artemis + +[Install] +WantedBy=multi-user.target diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/log4j2.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/log4j2.properties index a048b935b9a8..1ad0b28b7f59 100644 --- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/log4j2.properties +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/log4j2.properties @@ -75,6 +75,8 @@ appender.console.type=Console appender.console.name=console appender.console.layout.type=PatternLayout appender.console.layout.pattern=%d %-5level [%logger] %msg%n +appender.console.filter.threshold.type = ThresholdFilter +appender.console.filter.threshold.level = ${sys:artemis.console.level:-INFO} # Log file appender appender.log_file.type = RollingFile diff --git a/docs/user-manual/_book.adoc b/docs/user-manual/_book.adoc index 4f5e8160c0c5..6e6623518c36 100644 --- a/docs/user-manual/_book.adoc +++ b/docs/user-manual/_book.adoc @@ -38,6 +38,7 @@ include::proxy-protocol.adoc[leveloffset=1] //== Getting Started include::docker.adoc[leveloffset=1] +include::systemd.adoc[leveloffset=1] include::using-server.adoc[leveloffset=1] include::using-cli.adoc[leveloffset=1] include::client-classpath.adoc[leveloffset=1] diff --git a/docs/user-manual/logging.adoc b/docs/user-manual/logging.adoc index c3ae8b6cd3ea..f62c55608a19 100644 --- a/docs/user-manual/logging.adoc +++ b/docs/user-manual/logging.adoc @@ -125,6 +125,10 @@ appender.log_file.policies.cron.schedule = 0 0 0 * * ? appender.log_file.policies.cron.evaluateOnStartup = true ---- +While the default console log level is set to `INFO`, the console log level is set to `OFF` via additional java flags in the case of running Apache Artemis as a systemd service. +This is configured in the artemis.service systemd file: `JAVA_ARGS_APPEND=-Dartemis.console.level=OFF`. +This is to avoid the overhead of logging to both the console and the file. Any potential errors at startup will still be visible when checking the systemd service logs, by running `journalctl -u artemis` or similar commands. + == Configuring Broker Audit Logging There are 3 audit loggers that can be enabled separately and audit different types of broker events, these are: diff --git a/docs/user-manual/systemd.adoc b/docs/user-manual/systemd.adoc new file mode 100644 index 000000000000..9a15102a1d44 --- /dev/null +++ b/docs/user-manual/systemd.adoc @@ -0,0 +1,72 @@ += Systemd +:idprefix: +:idseparator: - +:docinfo: shared + +== Automatic systemd service generation + +A systemd service file is generated automatically when you create the broker via cli. The file is available at `pathToBrokerInstance/etc/artemis.service`. + +The contents of the automatically generated file look like this: + +[,console] + +---- +[Unit] +Description=Apache Artemis Service +After=network.target network-online.target +Wants=network-online.target + +[Service] +Type=exec +Environment=ARTEMIS_INSTANCE=/pathToBrokerInstance +Environment=JAVA_ARGS_APPEND=-Dartemis.console.level=OFF +ExecStart=/pathToBrokerInstance/bin/artemis run +TimeoutStopSec=60s +SuccessExitStatus=143 +KillMode=mixed +LimitMEMLOCK=infinity +LimitNOFILE=524288 +LimitNPROC=31438 +LimitAS=infinity +LimitFSIZE=infinity +SendSIGKILL=no +NotifyAccess=all +Restart=always +RestartSec=10 +#User=artemis +#Group=artemis + +[Install] +WantedBy=multi-user.target +---- + +You might want to tweak or add additional configuration inside the service file, for example: + +* uncomment and/or change `User` with which to run the service, to avoid running as root +* add `Nice` to tweak niceness of the java process +* add `Alias` +* increase or decrease `TimeoutStopSec` and `RestartSec` +* tweak the console log level, which is set to `OFF` to avoid the overhead of logging to both file and console + +You can read more about systemd https://systemd.io/[here]. + +== Enable systemd service via flags + +It is also possible to automate the service installation via `--enable-systemd-service` and `--systemd-service-name` flags. +In order to do this, the broker creation script usually needs to be run with `sudo` or `root` privileges. + +To enable the service automatically, and to change its name (from the default `artemis`), run: + +`./artemis create brokername --enable-systemd-service --systemd-service-name=artemisTest` + +You can now use `systemctl` commands to start/stop/restart the service, named `artemisTest`: + +[,console] +---- +systemctl start artemisTest +systemctl stop artemisTest +systemctl restart artemisTest +---- + +You can read the systemctl man page https://man7.org/linux/man-pages/man1/systemctl.1.html[here]. diff --git a/docs/user-manual/versions.adoc b/docs/user-manual/versions.adoc index fc14649009ea..6547ceff4b06 100644 --- a/docs/user-manual/versions.adoc +++ b/docs/user-manual/versions.adoc @@ -24,6 +24,14 @@ NOTE: Follow the general upgrade procedure outlined in the xref:upgrading.adoc#u * AMQP message size estimation has been improved to account for the full message metadata, fixing underestimates that occurred when messages carried large application properties. * AMQP broker connection Senders, Receivers and Peer configurations have been deprecated and will be removed in a future release. Users of the AMQP broker connection Senders and Receivers are encouraged to migrate to the newer AMQP Bridge configurations that provide more robust and configurable send to and receive from topologies. +* https://issues.apache.org/jira/browse/ARTEMIS-6261[ARTEMIS-6261] - Run artemis as a systemd service +Automated systemd unit file generation has been added to the broker instance creation tool. +The service is not enabled by default. +If running the creation script with `sudo` or `root` privileges, you can pass `--enable-systemd-service` to automatically enable the service at system startup. +The service file will then be placed in `/etc/systemd/system` and the commands `systemctl daemon-reload` and `systemctl enable artemis.service` will be executed automatically. +To customize the unit name use the `--systemd-service-name` flag (e.g., `--systemd-service-name=artemis-broker`). +The service can then be started/stopped by using commands like `systemctl start/stop artemis`. +This native systemd integration is preferred over the legacy `artemis-service` script. === Upgrading from 2.57.0