From ab8a4e411d44f72df671290331eb52ad740c824e Mon Sep 17 00:00:00 2001 From: gchuf Date: Wed, 16 Sep 2026 11:48:10 +0200 Subject: [PATCH 1/2] ARTEMIS-6261 Add the option to run artemis as a systemd service --- .../activemq/artemis/cli/commands/Create.java | 78 ++++++++++++++++++- .../artemis/cli/commands/etc/artemis.service | 35 +++++++++ .../cli/commands/etc/log4j2.properties | 2 + docs/user-manual/_book.adoc | 1 + docs/user-manual/logging.adoc | 4 + docs/user-manual/systemd.adoc | 63 +++++++++++++++ docs/user-manual/versions.adoc | 8 ++ 7 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.service create mode 100644 docs/user-manual/systemd.adoc 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/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..bdf7289d2c67 --- /dev/null +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.service @@ -0,0 +1,35 @@ +# 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.target 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 +Restart=always +RestartSec=10 + +[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..b164e4b283f2 --- /dev/null +++ b/docs/user-manual/systemd.adoc @@ -0,0 +1,63 @@ += 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 +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +---- + +You might want to tweak or add additional configuration inside the service file, for example: + +* add `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 From 8e5eda59d359d234e06a4b91e73d2859d566ee41 Mon Sep 17 00:00:00 2001 From: gchuf Date: Tue, 29 Sep 2026 14:15:56 +0200 Subject: [PATCH 2/2] ARTEMIS-6261 Copy the systemd service file to etc at upgrade --- .project | 11 +++++++++++ .../activemq/artemis/cli/commands/Upgrade.java | 14 ++++++++++++++ .../artemis/cli/commands/etc/artemis.service | 11 ++++++++++- docs/user-manual/systemd.adoc | 11 ++++++++++- 4 files changed, 45 insertions(+), 2 deletions(-) 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/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 index bdf7289d2c67..f2499245e4a4 100644 --- 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 @@ -17,7 +17,7 @@ [Unit] Description=Apache Artemis Service -After=network.target network-online.target +After=network-online.target Wants=network-online.target [Service] @@ -28,8 +28,17 @@ 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/docs/user-manual/systemd.adoc b/docs/user-manual/systemd.adoc index b164e4b283f2..9a15102a1d44 100644 --- a/docs/user-manual/systemd.adoc +++ b/docs/user-manual/systemd.adoc @@ -25,8 +25,17 @@ 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 @@ -34,7 +43,7 @@ WantedBy=multi-user.target You might want to tweak or add additional configuration inside the service file, for example: -* add `User` with which to run the service, to avoid running as root +* 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`