Skip to content

fix(dgw): overhaul Linux RPM/DEB packaging#1747

Merged
Benoît Cortier (CBenoit) merged 12 commits intomasterfrom
fix/systemd-npm
Apr 16, 2026
Merged

fix(dgw): overhaul Linux RPM/DEB packaging#1747
Benoît Cortier (CBenoit) merged 12 commits intomasterfrom
fix/systemd-npm

Conversation

@CBenoit
Copy link
Copy Markdown
Member

@CBenoit Benoît Cortier (CBenoit) commented Apr 9, 2026

Fix a systemd startup failure on RHEL/Rocky Linux caused by two ExecStart= directives: the postinst called devolutions-gateway service register which used ceviche to write both a base unit file and a drop-in without the mandatory ExecStart= reset, which systemd rejects.

RPM packaging changes:

  • Bundle the systemd unit file directly in the RPM (matching how the DEB already works via dh_installsystemd), eliminating the service register call
  • Ship a systemd preset file (85-devolutions-gateway.preset) so the service is correctly enabled on fresh install on RHEL/Rocky (which defaults to disable * for unknown units)
  • Use systemd-rpm-macros (%systemd_post, %systemd_preun, %systemd_postun_with_restart) for correct install/upgrade/remove semantics
  • Fix prerm to use RPM numeric arguments ($1) instead of Debian-style strings
  • Fix postrm to preserve config on removal (RPM has no purge concept)
  • Move daemon-reload to postrm so it runs after the unit file is removed
  • Config initialization now runs unconditionally (not gated on systemd)
  • Fresh install: enable only, do not start (service requires configuration)
  • Upgrade: try-restart only if already running
  • Config directory permissions hardened to 750

DEB packaging changes:

  • Mirror RPM service lifecycle semantics: enable on fresh install but do not start; try-restart on upgrade
  • Config initialization now runs unconditionally
  • Config directory permissions hardened to 750
  • Add Description= to service unit file

Both packages:

  • Print a post-install message on fresh install directing admin to edit gateway.json and start the service, with instruction varying based on whether systemd is present

main.rs:

  • Add ExecStart= reset line to the ceviche drop-in template so manually registered services (via service register) are also correct
  • Add Description= to the drop-in unit template

CI (tlk.ps1, package-gateway-rpm.ps1):

  • Fix --rpm-attr flags being placed after -- (fpm treated them as file paths)
  • Split fpm invocation into $FpmOptions and $FpmFiles arrays for clarity
  • Add service file and preset file to fpm gateway file list

Smoke tests (smoke-test-lib.sh, smoke-test-deb.sh, smoke-test-rpm.sh):

  • Add systemd_and_unit_available() helper that checks systemd presence AND unit file existence before any systemctl call
  • Add check_config_dir_permissions(): verifies 750
  • Add check_provisioner_key(): generates RSA-2048 key pair with openssl (required for the gateway to start)
  • Replace best-effort check_service_startup() with check_service_health(): starts via systemctl when available or directly otherwise, polls /jet/health, then stops — hard failure
  • Add uninstall test: removes package, verifies binary and unit file are gone, verifies config directory is preserved
  • Add curl and openssl to prerequisites
  • check_single_execstart() rewritten to use first-found unit file path (avoids /lib → /usr/lib symlink double-count) and ignores bare ExecStart= reset directives

linux-install-test.yml:

  • Expand matrix to Ubuntu 18.04/20.04/22.04/24.04 and Rocky Linux 8/9

Issue: DGW-338
Issue: DGW-366

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Devolutions Gateway Linux packaging to ship a static systemd unit file in the RPM (aligning with the DEB approach) and adjusts install/remove scripts and CI smoke tests to avoid the previous “multiple ExecStart” systemd rejection.

Changes:

  • Bundle devolutions-gateway.service directly into the RPM and set proper file attributes during packaging.
  • Update RPM maintainer scripts to manage the service via systemctl instead of devolutions-gateway service register/unregister.
  • Strengthen DEB/RPM smoke tests to require the unit file presence and add a regression check for ExecStart directives.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
package/Linux/gateway/rpm/service Adds a packaged systemd unit file for RPM installs.
package/Linux/gateway/rpm/prerm Switches uninstall behavior to systemctl stop/disable instead of binary-driven unregister.
package/Linux/gateway/rpm/postinst Removes service registration call; relies on packaged unit + systemctl.
package/Linux/gateway/debian/service Aligns DEB unit file metadata by adding Description=.
devolutions-gateway/src/main.rs Adjusts the Linux service registration unit template to clear ExecStart before setting it.
ci/package-gateway-rpm.ps1 Adds the unit file to the RPM payload and sets its RPM permissions/ownership.
.github/scripts/smoke-test-rpm.sh Updates RPM smoke test expectations to require the packaged unit file and adds ExecStart regression guard.
.github/scripts/smoke-test-lib.sh Adds check_single_execstart() helper used by DEB/RPM smoke tests.
.github/scripts/smoke-test-deb.sh Adds the ExecStart regression guard to the DEB smoke test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/scripts/smoke-test-lib.sh
Comment thread package/Linux/gateway/rpm/prerm Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/scripts/smoke-test-lib.sh Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package/Linux/gateway/rpm/postrm Outdated
Comment thread package/Linux/gateway/rpm/postrm Outdated
Comment thread package/Linux/gateway/rpm/prerm Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread devolutions-gateway/src/main.rs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package/Linux/gateway/rpm/postrm Outdated
Comment thread package/Linux/gateway/rpm/prerm Outdated
Fix a systemd startup failure on RHEL/Rocky Linux caused by two ExecStart=
directives: the postinst called `devolutions-gateway service register` which
used ceviche to write both a base unit file and a drop-in without the
mandatory ExecStart= reset, which systemd rejects.

RPM packaging changes:
- Bundle the systemd unit file directly in the RPM (matching how the DEB
  already works via dh_installsystemd), eliminating the service register call
- Ship a systemd preset file (85-devolutions-gateway.preset) so the service
  is correctly enabled on fresh install on RHEL/Rocky (which defaults to
  disable * for unknown units)
- Use systemd-rpm-macros (%systemd_post, %systemd_preun,
  %systemd_postun_with_restart) for correct install/upgrade/remove semantics
- Fix prerm to use RPM numeric arguments ($1) instead of Debian-style strings
- Fix postrm to preserve config on removal (RPM has no purge concept)
- Move daemon-reload to postrm so it runs after the unit file is removed
- Config initialization now runs unconditionally (not gated on systemd)
- Fresh install: enable only, do not start (service requires configuration)
- Upgrade: try-restart only if already running
- Config directory permissions hardened to 750

DEB packaging changes:
- Mirror RPM service lifecycle semantics: enable on fresh install but do not
  start; try-restart on upgrade
- Config initialization now runs unconditionally
- Config directory permissions hardened to 750
- Add Description= to service unit file

Both packages:
- Print a post-install message on fresh install directing admin to edit
  gateway.json and start the service, with instruction varying based on
  whether systemd is present

main.rs:
- Add ExecStart= reset line to the ceviche drop-in template so manually
  registered services (via `service register`) are also correct
- Add Description= to the drop-in unit template

CI (tlk.ps1, package-gateway-rpm.ps1):
- Fix --rpm-attr flags being placed after -- (fpm treated them as file paths)
- Split fpm invocation into $FpmOptions and $FpmFiles arrays for clarity
- Add service file and preset file to fpm gateway file list

Smoke tests (smoke-test-lib.sh, smoke-test-deb.sh, smoke-test-rpm.sh):
- Add systemd_and_unit_available() helper that checks systemd presence AND
  unit file existence before any systemctl call
- Add check_config_dir_permissions(): verifies 750
- Add check_provisioner_key(): generates RSA-2048 key pair with openssl
  (required for the gateway to start)
- Replace best-effort check_service_startup() with check_service_health():
  starts via systemctl when available or directly otherwise, polls
  /jet/health, then stops — hard failure
- Add uninstall test: removes package, verifies binary and unit file are
  gone, verifies config directory is preserved
- Add curl and openssl to prerequisites
- check_single_execstart() rewritten to use first-found unit file path
  (avoids /lib → /usr/lib symlink double-count) and ignores bare
  ExecStart= reset directives

linux-install-test.yml:
- Expand matrix to Ubuntu 18.04/20.04/22.04/24.04 and Rocky Linux 8/9/10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/scripts/smoke-test-rpm.sh
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/scripts/smoke-test-deb.sh Outdated
Comment thread package/Linux/gateway/debian/postinst Outdated
Comment thread .github/scripts/smoke-test-rpm.sh Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package/Linux/gateway/debian/postinst
@CBenoit Benoît Cortier (CBenoit) enabled auto-merge (squash) April 16, 2026 14:55
@CBenoit
Copy link
Copy Markdown
Member Author

Confirmed working as expected end-to-end:

Rocky Linux (~RHEL)

$ sudo dnf install ./devolutions-gateway_2026.1.1-1_x86_64.rpm
=================================================================================================================================================================================================================
 Package                                                  Architecture                                Version                                            Repository                                         Size
=================================================================================================================================================================================================================
Installing:
 devolutions-gateway                                      x86_64                                      2026.1.1-1                                         @commandline                                       22 M

Transaction Summary
=================================================================================================================================================================================================================
Install  1 Package

Total size: 22 M
Installed size: 60 M
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                                                                         1/1
  Installing       : devolutions-gateway-2026.1.1-1.x86_64                                                                                                                                                   1/1
  Running scriptlet: devolutions-gateway-2026.1.1-1.x86_64                                                                                                                                                   1/1

Devolutions Gateway has been installed.
Edit /etc/devolutions-gateway/gateway.json, then run:
  systemctl start devolutions-gateway



Installed:
  devolutions-gateway-2026.1.1-1.x86_64

Complete!
$ ls /etc/devolutions-gateway/
ls: cannot open directory '/etc/devolutions-gateway/': Permission denied
$ sudo ls /etc/devolutions-gateway/
gateway.json
$ sudo cp ./provisioner.pem /etc/devolutions-gateway/
$ sudo ls /etc/devolutions-gateway/
gateway.json  provisioner.pem
$ sudo systemctl start devolutions-gateway.service
$ curl http://127.0.0.1:7171/jet/health
Devolutions Gateway "DEVOLUTIONSXXX" is alive and healthy.
$ sudo systemctl stop devolutions-gateway.service
$ curl http://127.0.0.1:7171/jet/health
curl: (7) Failed to connect to 127.0.0.1 port 7171 after 0 ms: Could not connect to server
$ sudo dnf remove devolutions-gateway
Dependencies resolved.
=================================================================================================================================================================================================================
 Package                                                  Architecture                                Version                                           Repository                                          Size
=================================================================================================================================================================================================================
Removing:
 devolutions-gateway                                      x86_64                                      2026.1.1-1                                        @@commandline                                       60 M

Transaction Summary
=================================================================================================================================================================================================================
Remove  1 Package

Freed space: 60 M
Is this ok [y/N]: y
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                                                                         1/1
  Running scriptlet: devolutions-gateway-2026.1.1-1.x86_64                                                                                                                                                   1/1
  Erasing          : devolutions-gateway-2026.1.1-1.x86_64                                                                                                                                                   1/1
  Running scriptlet: devolutions-gateway-2026.1.1-1.x86_64                                                                                                                                                   1/1

Removed:
  devolutions-gateway-2026.1.1-1.x86_64

Complete!

Ubuntu

$ sudo apt install ./devolutions-gateway_2026.1.1-1_amd64.deb
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Note, selecting 'devolutions-gateway' instead of './devolutions-gateway_2026.1.1-1_amd64.deb'
The following NEW packages will be installed:
  devolutions-gateway
0 upgraded, 1 newly installed, 0 to remove and 9 not upgraded.
Need to get 0 B/16.8 MB of archives.
After this operation, 62.6 MB of additional disk space will be used.
Get:1 /home/bcortier/devolutions-gateway_2026.1.1-1_amd64.deb devolutions-gateway amd64 2026.1.1-1 [16.8 MB]
Selecting previously unselected package devolutions-gateway.
(Reading database ... 48302 files and directories currently installed.)
Preparing to unpack .../devolutions-gateway_2026.1.1-1_amd64.deb ...
Unpacking devolutions-gateway (2026.1.1-1) ...
Setting up devolutions-gateway (2026.1.1-1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/devolutions-gateway.service → /usr/lib/systemd/system/devolutions-gateway.service.

Devolutions Gateway has been installed.
Edit /etc/devolutions-gateway/gateway.json, then run:
  systemctl start devolutions-gateway
$ ls /etc/devolutions-gateway/
ls: cannot open directory '/etc/devolutions-gateway/': Permission denied
$ sudo ls /etc/devolutions-gateway/
gateway.json
$ sudo cp ./provisioner.pem /etc/devolutions-gateway/
$ sudo ls /etc/devolutions-gateway/
gateway.json  provisioner.pem
$ systemctl start devolutions-gateway
Failed to start devolutions-gateway.service: Interactive authentication required.
See system logs and 'systemctl status devolutions-gateway.service' for details.
$ sudo systemctl start devolutions-gateway
$ curl http://127.0.0.1:7171/jet/health
Devolutions Gateway "DEVOLUTIONSXXX" is alive and healthy.
$ sudo systemctl stop devolutions-gateway.service
$ curl http://127.0.0.1:7171/jet/health
curl: (7) Failed to connect to 127.0.0.1 port 7171 after 0 ms: Couldn't connect to server
$ sudo apt purge devolutions-gateway
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages will be REMOVED:
  devolutions-gateway*
0 upgraded, 0 newly installed, 1 to remove and 9 not upgraded.
After this operation, 62.6 MB disk space will be freed.
Do you want to continue? [Y/n]
(Reading database ... 48406 files and directories currently installed.)
Removing devolutions-gateway (2026.1.1-1) ...
(Reading database ... 48302 files and directories currently installed.)
Purging configuration files for devolutions-gateway (2026.1.1-1) ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved on the basis that the smoke tests validate that it all works as expected

@CBenoit Benoît Cortier (CBenoit) merged commit a18e6b9 into master Apr 16, 2026
87 of 90 checks passed
@CBenoit Benoît Cortier (CBenoit) deleted the fix/systemd-npm branch April 16, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants