From b6db4d342bdd1845c7ff5740ef69e6bf5afaf091 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 04 Sep 2026 08:12:29 +0000
Subject: [PATCH] Modernize Debian and RPM packaging (systemd, dedicated user, CI tests) (#663)

---
 .github/workflows/build.yml |  237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 236 insertions(+), 1 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 90ecc71..efb811e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,7 +24,8 @@
   cancel-in-progress: true
 
 # Nothing in this workflow writes back to the repository: the docker jobs push to
-# the local registry service, not to a remote one. The docker jobs additionally get
+# the local registry service, not to a remote one, and the other jobs only
+# publish artifacts through the actions API. The docker jobs additionally get
 # security-events: write to upload Trivy scan results to code scanning.
 permissions:
   contents: read
@@ -680,6 +681,240 @@
           if-no-files-found: warn
           retention-days: 90
 
+  test-deb:
+    needs: build-maven
+    runs-on: 'ubuntu-latest'
+    steps:
+      - uses: actions/checkout@v6
+      - name: Download artifacts
+        uses: actions/download-artifact@v8
+        with:
+          name: ubuntu-latest-11
+      - name: Clean-room install + SysV start/stop (debian:12 container)
+        shell: bash
+        run: |
+          docker run --rm -v "$PWD:/work" -w /work debian:12 bash -c '
+            set -e
+            export DEBIAN_FRONTEND=noninteractive
+            DEB=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
+            echo "Found $DEB"
+            apt-get update
+            apt-get install -y lintian
+            lintian --info --no-tag-display-limit "$DEB" || true
+            dpkg-deb -I "$DEB"
+            # Both service files must be present (two greps: a single alternation
+            # would pass with either one missing).
+            dpkg-deb -c "$DEB" | grep "lib/systemd/system/opendj.service"
+            dpkg-deb -c "$DEB" | grep "etc/init.d/opendj"
+            # /etc/default/opendj must be a real conffile, so admin edits survive
+            # upgrades and the file survives "apt remove".
+            dpkg-deb -e "$DEB" /tmp/ctrl
+            grep -qx "/etc/default/opendj" /tmp/ctrl/conffiles
+            apt-get install -y "./$DEB"
+            id opendj
+            test "$(stat -c %U /opt/opendj)" = opendj
+            # No JAVA_HOME in this clean container: Java resolves from the PATH
+            # java that the package JRE dependency pulled in.
+            runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
+              --rootUserDN "cn=Directory Manager" --rootUserPassword password \
+              --hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
+              --baseDN dc=example,dc=com --addBaseEntry
+            /etc/init.d/opendj start
+            bash .github/scripts/wait-for-ldap.sh 1389
+            /etc/init.d/opendj status
+            /etc/init.d/opendj stop
+            apt-get purge -y opendj
+          '
+      - name: Live systemd install + start/stop (runner)
+        shell: bash
+        run: |
+          DEB=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
+          sudo apt-get update
+          sudo apt-get install -y "$PWD/$DEB"
+          test "$(stat -c '%U' /opt/opendj)" = opendj
+          # postinst must have enabled the unit; do not enable by hand here or
+          # the postinst registration would go untested.
+          sudo systemctl is-enabled --quiet opendj
+          # Before setup the unit's start condition must keep it from
+          # start-bursting: "start" succeeds but no process may appear.
+          sudo systemctl start opendj
+          sleep 2
+          if sudo systemctl is-failed --quiet opendj; then echo "unit failed before setup"; exit 1; fi
+          # sudo/runuser/systemd strip JAVA_HOME -> the service uses the PATH java.
+          # Privileged port 389: only AmbientCapabilities=CAP_NET_BIND_SERVICE lets
+          # the non-root service bind it - the very reason it can drop root. setup
+          # itself runs via runuser WITHOUT that capability, so it must skip its
+          # own bind probe; the systemd start below performs the real 389 bind.
+          sudo runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
+            --rootUserDN "cn=Directory Manager" --rootUserPassword password \
+            --hostname localhost --ldapPort 389 --adminConnectorPort 4444 \
+            --baseDN dc=example,dc=com --addBaseEntry --skipPortCheck
+          sudo systemctl start opendj
+          bash .github/scripts/wait-for-ldap.sh 389
+          sudo systemctl is-active --quiet opendj
+          echo "OpenDJ is active under systemd"
+          sudo systemctl stop opendj
+          sleep 3
+          if sudo systemctl is-active --quiet opendj; then echo "still active"; exit 1; fi
+          sudo apt-get purge -y opendj
+
+  # Known coverage limit: the container has no booted systemd (/run/systemd/system
+  # is absent), so only the SysV/chkconfig path of the scriptlets is exercised
+  # here; start/stop through systemd is covered by the deb live-systemd jobs on
+  # the runner. The enable registration IS asserted below - systemctl enable is
+  # a pure symlink operation and works without a booted systemd.
+  test-rpm:
+    needs: build-maven
+    runs-on: 'ubuntu-latest'
+    steps:
+      - uses: actions/checkout@v6
+      - name: Download artifacts
+        uses: actions/download-artifact@v8
+        with:
+          name: ubuntu-latest-11
+      - name: Install and start/stop in Rocky Linux 9
+        shell: bash
+        run: |
+          docker run --rm -v "$PWD:/work" -w /work rockylinux:9 bash -c '
+            set -e
+            RPM=$(ls opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm | head -1)
+            echo "Found $RPM"
+            # Both service files must be shipped.
+            rpm -qlp "$RPM" | grep "^/usr/lib/systemd/system/opendj.service$"
+            rpm -qlp "$RPM" | grep "^/etc/init.d/opendj$"
+            # The %pre user-creation dependency must be declared for minimal images.
+            rpm -qp --requires "$RPM" | grep "shadow-utils"
+            # A real EL9 host always has systemd, but the container image ships no
+            # systemctl at all - %post then (correctly) skips the enable in silence.
+            # Preinstall it as host infrastructure, deliberately NOT a package
+            # Requires, so the is-enabled assertion below tests the enable path.
+            dnf install -y systemd
+            # No further manual dependencies: the package must pull everything else
+            # itself (Requires).
+            dnf install -y "$RPM"
+            id opendj
+            test "$(stat -c %U /opt/opendj)" = opendj
+            # %post must have enabled the unit on initial install.
+            systemctl is-enabled opendj.service
+            # No JAVA_HOME here: Java resolves from the PATH java pulled in by
+            # the java-headless dependency.
+            runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
+              --rootUserDN "cn=Directory Manager" --rootUserPassword password \
+              --hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
+              --baseDN dc=example,dc=com --addBaseEntry
+            /etc/init.d/opendj start
+            bash .github/scripts/wait-for-ldap.sh 1389
+            /etc/init.d/opendj status
+            /etc/init.d/opendj stop
+            rpm -e opendj
+          '
+
+  # Upgrade path: released 5.1.2 deb (root-owned, SysV) -> this build's deb, on the
+  # runner's LIVE systemd. The new package must stop the running server, create the
+  # opendj user, migrate ownership, run the upgrade tool and restart the server with
+  # the old data - through deb-systemd-invoke and the native unit, the path a real
+  # systemd host takes (docs: chap-upgrade).
+  test-deb-upgrade:
+    needs: build-maven
+    runs-on: 'ubuntu-latest'
+    steps:
+      - uses: actions/checkout@v6
+      - name: Download artifacts
+        uses: actions/download-artifact@v8
+        with:
+          name: ubuntu-latest-11
+      - name: Download released 5.1.2 deb
+        shell: bash
+        run: |
+          curl -fsSL -o opendj-5.1.2.deb https://github.com/OpenIdentityPlatform/OpenDJ/releases/download/5.1.2/opendj_5.1.2-1_all.deb
+          # Pin the upgrade source: a retagged release asset must fail loudly here,
+          # not surface as an unrelated upgrade-job failure.
+          echo "8f7f8bdd526b2d63eaef0621545be6aa55749cb60200d3bc2a2849d9ab69eb59  opendj-5.1.2.deb" | sha256sum -c -
+      - name: Upgrade 5.1.2 -> new deb (live systemd on the runner)
+        shell: bash
+        run: |
+          NEW=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
+          echo "New deb: $NEW"
+          sudo apt-get update
+          # No manual dependencies: even the released 5.1.2 deb declares a JRE dependency.
+          sudo apt-get install -y "$PWD/opendj-5.1.2.deb"
+          # 5.1.2 model: no dedicated user, root-owned tree, SysV only - on this
+          # booted host its unit comes from the systemd-sysv-generator.
+          sudo /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
+            --rootUserDN "cn=Directory Manager" --rootUserPassword password \
+            --hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
+            --baseDN dc=example,dc=com --addBaseEntry
+          sudo systemctl daemon-reload
+          sudo /etc/init.d/opendj start
+          bash .github/scripts/wait-for-ldap.sh 1389
+          # Leave the server RUNNING: the new package must stop it, upgrade and
+          # restart it - through deb-systemd-invoke and the native unit this time.
+          sudo apt-get install -y "$PWD/$NEW"
+          id opendj
+          test "$(stat -c %U /opt/opendj)" = opendj
+          test -f /opt/opendj/config/config.ldif
+          # The systemd-hosted upgrade must leave the unit enabled and active.
+          sudo systemctl is-enabled --quiet opendj
+          sudo systemctl is-active --quiet opendj
+          # The pre-upgrade data must be served again, now by the dedicated user.
+          bash .github/scripts/wait-for-ldap.sh 1389
+          test "$(stat -c %U /proc/$(cat /opt/opendj/logs/server.pid))" = opendj
+          sudo systemctl stop opendj
+          sleep 3
+          if sudo systemctl is-active --quiet opendj; then echo "still active"; exit 1; fi
+          sudo apt-get purge -y opendj
+
+  test-rpm-upgrade:
+    needs: build-maven
+    runs-on: 'ubuntu-latest'
+    steps:
+      - uses: actions/checkout@v6
+      - name: Download artifacts
+        uses: actions/download-artifact@v8
+        with:
+          name: ubuntu-latest-11
+      - name: Download released 5.1.2 rpm
+        shell: bash
+        run: |
+          curl -fsSL -o opendj-5.1.2.rpm https://github.com/OpenIdentityPlatform/OpenDJ/releases/download/5.1.2/opendj-5.1.2-1.noarch.rpm
+          # Pin the upgrade source: a retagged release asset must fail loudly here,
+          # not surface as an unrelated upgrade-job failure.
+          echo "9622ef7c3292f0c156c96029a647ca704064aa93b567e5608734fe07d224ec58  opendj-5.1.2.rpm" | sha256sum -c -
+      - name: Upgrade 5.1.2 -> new rpm (Rocky Linux 9 container)
+        shell: bash
+        run: |
+          docker run --rm -v "$PWD:/work" -w /work rockylinux:9 bash -c '
+            set -e
+            NEW=$(ls opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm | head -1)
+            echo "New rpm: $NEW"
+            # No manual dependencies: the released 5.1.2 rpm already declares its runtime
+            # requirements (#677), and everything the NEW package needs must come from its
+            # own Requires.
+            dnf install -y ./opendj-5.1.2.rpm
+            # 5.1.2 model: no dedicated user, root-owned tree, SysV only
+            /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
+              --rootUserDN "cn=Directory Manager" --rootUserPassword password \
+              --hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
+              --baseDN dc=example,dc=com --addBaseEntry
+            /etc/init.d/opendj start
+            bash .github/scripts/wait-for-ldap.sh 1389
+            # Leave the server RUNNING: the new package must stop it, upgrade and restart it.
+            dnf install -y "./$NEW"
+            id opendj
+            test "$(stat -c %U /opt/opendj)" = opendj
+            test -f /opt/opendj/config/config.ldif
+            # 5.1.2 was chkconfig-enabled and the native unit now shadows the
+            # sysv-generator unit: %post must have migrated that enable state
+            # (a pure symlink operation, asserted fine without booted systemd).
+            systemctl is-enabled opendj.service
+            # The package restarted the server; the pre-upgrade data must be served again,
+            # now by the dedicated user.
+            bash .github/scripts/wait-for-ldap.sh 1389
+            test "$(stat -c %U /proc/$(cat /opt/opendj/logs/server.pid))" = opendj
+            /etc/init.d/opendj stop
+            rpm -e opendj
+          '
+
   test-msi:
     needs: build-maven
     runs-on: 'windows-latest'

--
Gitblit v1.10.0