From d7d79e8e871b64f7bd6218d26e152ce94ed18ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rolf=20Schr=C3=B6der?= Date: Fri, 3 Apr 2026 07:39:11 +0200 Subject: [PATCH] geoserver: add service --- doc/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/geoserver.nix | 81 +++++++++++++++++++ nixos/tests/geoserver.nix | 75 +++++++++++------ 4 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 nixos/modules/services/web-apps/geoserver.nix diff --git a/doc/release-notes/rl-2605.section.md b/doc/release-notes/rl-2605.section.md index b7aa319a57e6..a6ef5ed6d887 100644 --- a/doc/release-notes/rl-2605.section.md +++ b/doc/release-notes/rl-2605.section.md @@ -258,6 +258,8 @@ - The `services.nextcloud-spreed-signaling` NixOS module has been added to facilitate declarative management of a standalone Spreed signaling server ("High Performance Backend" for Nextcloud Talk). +- The `services.geoserver` NixOS module has been added to allow running [Geoserver](https://geoserver.org/) as a service. + - `fetchPnpmDeps` and `pnpmConfigHook` were added as top-level attributes, replacing the now deprecated `pnpm.fetchDeps` and `pnpm.configHook` attributes. - `buildNpmPackage` now supports `npmDepsFetcherVersion` (and `fetchNpmDeps` now supports `fetcherVersion`). Set to `2` to enable packument caching, which fixes builds for projects using npm workspaces. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c1056ec727eb..74ad1699c9bf 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1644,6 +1644,7 @@ ./services/web-apps/froide-govplan.nix ./services/web-apps/galene.nix ./services/web-apps/gancio.nix + ./services/web-apps/geoserver.nix ./services/web-apps/gerrit.nix ./services/web-apps/glance.nix ./services/web-apps/glitchtip.nix diff --git a/nixos/modules/services/web-apps/geoserver.nix b/nixos/modules/services/web-apps/geoserver.nix new file mode 100644 index 000000000000..578c6d69c9bc --- /dev/null +++ b/nixos/modules/services/web-apps/geoserver.nix @@ -0,0 +1,81 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + + cfg = config.services.geoserver; + +in +{ + options = { + services.geoserver = { + enable = mkEnableOption "Geoserver service"; + + package = lib.mkPackageOption pkgs "geoserver" { }; + + user = mkOption { + type = types.str; + default = "geoserver"; + description = "The (system) user that will run the service."; + }; + + group = mkOption { + type = types.str; + default = "geoserver"; + description = "The user's group."; + }; + + jvmOpts = mkOption { + type = types.lines; + default = ""; + description = "Any options passed to the JVM via the `JAVA_OPTS` environment variable. See [startup.sh](https://github.com/geoserver/geoserver/blob/main/src/release/bin/startup.sh) for details."; + }; + + jettyOpts = mkOption { + type = types.lines; + default = ""; + example = "jetty.http.port=1234"; + description = "Any options passed to the Jetty web server via the `JETTY_OPTS` environment variable. See [startup.sh](https://github.com/geoserver/geoserver/blob/main/src/release/bin/startup.sh) for details."; + }; + }; + }; + + config = mkIf cfg.enable { + + users.users."${cfg.user}" = { + group = cfg.group; + isSystemUser = true; + }; + users.groups."${cfg.group}" = { }; + + systemd.services.geoserver = { + description = "Geoserver"; + + wantedBy = [ "multi-user.target" ]; + + environment = { + GEOSERVER_HOME = "${cfg.package}/share/geoserver"; + GEOSERVER_DATA_DIR = "/var/lib/geoserver"; + JAVA_OPTS = "${cfg.jvmOpts}"; + JETTY_OPTS = "${cfg.jettyOpts}"; + }; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/geoserver-startup"; + User = cfg.user; + Group = cfg.group; + NoNewPrivileges = true; + ProtectHome = true; # true=deny access to /home, /root, /run/user + StateDirectory = "geoserver"; + }; + }; + }; + + meta.teams = [ lib.teams.geospatial ]; +} diff --git a/nixos/tests/geoserver.nix b/nixos/tests/geoserver.nix index 9f1369ecb95e..06bc8ec5a54c 100644 --- a/nixos/tests/geoserver.nix +++ b/nixos/tests/geoserver.nix @@ -28,59 +28,86 @@ in { virtualisation.diskSize = 2 * 1024; + # The VM has several Geoserver package installations. They can't be run + # in parallel but are launched sequentially during the test run. + # Context managers (see below) are used to start/stop them selectively. environment.systemPackages = [ geoserver - geoserverWithImporterExtension geoserverWithAllExtensions ]; + + services.geoserver = { + enable = true; + jettyOpts = "jetty.http.port=8090"; + package = geoserverWithImporterExtension; + }; + systemd.services.geoserver.wantedBy = lib.mkForce [ ]; # do not start the service at boot }; }; testScript = '' from contextlib import contextmanager - curl_cmd = "curl --fail --connect-timeout 2" - curl_cmd_rest = f"{curl_cmd} -u admin:geoserver -X GET" - base_url = "http://localhost:8080/geoserver" + CURL_CMD = "curl --fail --connect-timeout 2" + CURL_CMD_REST = f"{CURL_CMD} -u admin:geoserver -X GET" + BASE_URL_PKG = "http://localhost:8080/geoserver" + BASE_URL_SERVICE = "http://localhost:8090/geoserver" log_file = "./log.txt" @contextmanager - def running_geoserver(pkg): + def running_geoserver_pkg(pkg): try: print(f"Launching geoserver from {pkg}...") machine.execute(f"{pkg}/bin/geoserver-startup > {log_file} 2>&1 &") - machine.wait_until_succeeds(f"{curl_cmd} {base_url} 2>&1", timeout=60) + machine.wait_until_succeeds(f"{CURL_CMD} {BASE_URL_PKG} 2>&1", timeout=60) yield finally: # We need to wait a little bit to make sure the server is properly # shutdown before launching a new instance. machine.execute(f"{pkg}/bin/geoserver-shutdown; sleep 1") + @contextmanager + def running_geoserver_service(): + service_name = "geoserver" + try: + print(f"Launching service {service_name}...") + machine.execute(f"systemctl start {service_name} > tee {log_file} 2>&1") + machine.wait_until_succeeds(f"{CURL_CMD} {BASE_URL_SERVICE} 2>&1", timeout=60) + yield + finally: + # We need to wait a little bit to make sure the server is properly + # shutdown before launching a new instance. + machine.execute(f"systemctl stop {service_name}; sleep 1") + start_all() - with running_geoserver("${geoserver}"): - machine.succeed(f"{curl_cmd} {base_url}/ows?service=WMS&version=1.3.0&request=GetCapabilities") + with subtest("A standalone Geoserver installation without extensions can be started."): + with running_geoserver_pkg("${geoserver}"): + machine.succeed(f"{CURL_CMD} {BASE_URL_PKG}/ows?service=WMS&version=1.3.0&request=GetCapabilities") - # No extensions yet. - machine.fail(f"{curl_cmd_rest} {base_url}/rest/imports") - machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv") + # No extensions yet. + machine.fail(f"{CURL_CMD_REST} {BASE_URL_PKG}/rest/imports") + machine.fail(f"{CURL_CMD_REST} {BASE_URL_PKG}/rest/monitor/requests.csv") + with subtest("A standalone Geoserver installation with numerous extensions can be started."): + with running_geoserver_pkg("${geoserverWithAllExtensions}"): + machine.succeed(f"{CURL_CMD_REST} {BASE_URL_PKG}/rest/imports") + machine.succeed(f"{CURL_CMD_REST} {BASE_URL_PKG}/rest/monitor/requests.csv") + _, stdout = machine.execute(f"cat {log_file}") + print(stdout.replace("\\n", "\n")) + assert "GDAL Native Library loaded" in stdout, "gdal" + assert "org.geotools.imageio.netcdf.utilities.NetCDFUtilities" in stdout, "netcdf" + assert "Unable to load library 'netcdf'" not in stdout, "netcdf" - with running_geoserver("${geoserverWithImporterExtension}"): - machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports") - machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv") + # libjpeg-turbo is disabled as of 2.28.1. + # assert "The turbo jpeg encoder is available for usage" in stdout, "libjpeg-turbo" - with running_geoserver("${geoserverWithAllExtensions}"): - machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports") - machine.succeed(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv") - _, stdout = machine.execute(f"cat {log_file}") - print(stdout.replace("\\n", "\n")) - assert "GDAL Native Library loaded" in stdout, "gdal" - assert "org.geotools.imageio.netcdf.utilities.NetCDFUtilities" in stdout, "netcdf" - assert "Unable to load library 'netcdf'" not in stdout, "netcdf" + with subtest("Geoserver can be run as a service. Some Extensions are available."): + with running_geoserver_service(): + # Only the importer extension is available. + machine.succeed(f"{CURL_CMD_REST} {BASE_URL_SERVICE}/rest/imports") + machine.fail(f"{CURL_CMD_REST} {BASE_URL_SERVICE}/rest/monitor/requests.csv") - # libjpeg-turbo is disabled as of 2.28.1. - # assert "The turbo jpeg encoder is available for usage" in stdout, "libjpeg-turbo" ''; }