Skip to content

Add helper that enables systemd services created by the system-controller container - #2548

Open
nluaces wants to merge 8 commits into
skupperproject:mainfrom
nluaces:add-systmd-service-per-router
Open

Add helper that enables systemd services created by the system-controller container#2548
nluaces wants to merge 8 commits into
skupperproject:mainfrom
nluaces:add-systmd-service-per-router

Conversation

@nluaces

@nluaces nluaces commented Aug 4, 2026

Copy link
Copy Markdown
Member

Resolves #2490


A new skupper-site-service-enabler.service unit is installed when running skupper system install --reload-type auto and removed when running skupper system uninstall --force.


Summary by CodeRabbit

  • New Features

    • Added automatic monitoring and management of site services in systemd-based environments.
    • Site services are enabled, started, and restarted automatically when changes are detected.
    • Supports both system-wide and per-user service installations.
    • Container information now includes the current state.
  • Bug Fixes

    • Improved detection and reporting of container status.
    • Installation and removal now report failures clearly and stop safely when cleanup steps fail.
    • Uninstallation avoids leaving managed services behind.

@nluaces nluaces self-assigned this Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds a host-side systemd service enabler for automatic reloads. It synchronizes site service units, supports root and non-root systemd paths, reports installation and removal errors, and returns container state.

Changes

Site service enabler

Layer / File(s) Summary
Service synchronization
internal/nonkube/bootstrap/site_service_enabler_script.template, internal/nonkube/bootstrap/site_service_enabler_service.template
The generated script identifies managed units by ownership markers, installs changed units, restarts updated services, and removes missing units. The systemd template defines ordering, restart behavior, and installation targets.
Systemd installation and cleanup
internal/nonkube/bootstrap/site_service_enabler_installer.go, internal/nonkube/bootstrap/site_service_enabler_installer_test.go
The installer renders files, selects root or non-root systemd paths, executes systemctl commands, and returns errors. Removal stops and disables existing units before deleting files and reloading systemd. Tests cover command order, rendering, permissions, and failure handling.
Installation lifecycle and container state
internal/nonkube/bootstrap/install.go, internal/nonkube/bootstrap/uninstall.go, internal/nonkube/client/compat/container.go, pkg/container/client.go, internal/kube/certificates/mgr_test.go
Automatic reload setup installs the enabler for existing and newly created controllers. Uninstall stops when enabler removal fails. Container checks return the container state. A helper comment typo is corrected.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to 3aa40

The PR adds and removes a host-side systemd helper, but current uninstall ordering can remove it for the wrong platform, while failure paths may leave configuration unapplied or report successful removal when the unit remains installed. These bounded service-management risks require follow-up before merge.

Sequence Diagram(s)

sequenceDiagram
  participant Install
  participant SiteServiceEnablerInstaller
  participant systemd
  participant SiteServiceEnablerScript
  Install->>SiteServiceEnablerInstaller: install automatic reload support
  SiteServiceEnablerInstaller->>SiteServiceEnablerScript: render and write wrapper
  SiteServiceEnablerInstaller->>systemd: reload, enable, and start enabler
  SiteServiceEnablerScript->>systemd: install and synchronize site units
Loading

Suggested reviewers: fgiorgetti, c-kruse

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes support issue #2490, but the typo correction in internal/kube/certificates/mgr_test.go is unrelated to the systemd service enabler feature. Remove the unrelated comment typo correction from this pull request, or move it to a separate pull request.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 53 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a helper that enables systemd services created by the system-controller container.
Linked Issues check ✅ Passed The changes satisfy issue #2490. They install the host-side helper for automatic reloads, monitor and manage namespace service units, and remove the helper during uninstallation.
  • Fix all pre-merge checks with AI

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fcea0e92-8ae3-4ba5-83bd-e45997ba52b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8fcff84 and 7c16669.

📒 Files selected for processing (9)
  • internal/cmd/skupper/system/nonkube/site_service_enabler.go
  • internal/cmd/skupper/system/system.go
  • internal/nonkube/bootstrap/install.go
  • internal/nonkube/bootstrap/site_service_enabler_installer.go
  • internal/nonkube/bootstrap/site_service_enabler_installer_test.go
  • internal/nonkube/bootstrap/site_service_enabler_service.template
  • internal/nonkube/bootstrap/uninstall.go
  • internal/nonkube/enabler/enabler.go
  • internal/nonkube/enabler/enabler_test.go

Comment thread internal/nonkube/bootstrap/install.go
Comment thread internal/nonkube/bootstrap/site_service_enabler_installer_test.go
Comment thread internal/nonkube/bootstrap/site_service_enabler_installer.go Outdated
Comment thread internal/nonkube/bootstrap/site_service_enabler_installer.go Outdated
Comment thread internal/nonkube/bootstrap/uninstall.go Outdated
Comment thread internal/nonkube/enabler/enabler.go Outdated
Comment thread internal/nonkube/enabler/enabler.go Outdated
@nluaces

nluaces commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai generate sequence diagram

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Sequence Diagram for PR

sequenceDiagram
    participant User
    participant SkupperCLI as skupper CLI
    participant Installer as Site Service Enabler Installer
    participant HostFS as Host filesystem
    participant Systemd as systemd
    participant Wrapper as site-service-enabler.service
    participant Daemon as _site-service-enabler
    participant Watcher as Namespace script watcher
    participant Controller as System controller
    participant Enabler as Service enabler

    User->>SkupperCLI: system install --reload-type auto
    SkupperCLI->>Installer: Install site service enabler
    Installer->>HostFS: Install wrapper and unit template
    Installer->>HostFS: Select root or user systemd paths
    Installer->>Systemd: Reload unit configuration
    Installer->>Systemd: Enable and start helper service

    Systemd->>Wrapper: Start service
    Wrapper->>Daemon: Launch hidden _site-service-enabler command
    Daemon->>Watcher: Watch namespaces directory
    Watcher-->>Daemon: Watchers active

    Controller->>HostFS: Create namespace/internal/scripts/skupper-{namespace}.service
    HostFS-->>Watcher: Script directory or service file event
    Watcher->>Enabler: Forward service file change
    Enabler->>HostFS: Copy service into systemd unit directory
    Enabler->>Systemd: Reload systemd units
    Enabler->>Systemd: Enable skupper-{namespace}.service
    Enabler->>Systemd: Start or restart service

    Controller->>HostFS: Modify or remove service file
    HostFS-->>Watcher: File change or removal event
    Watcher->>Enabler: Forward update
    Enabler->>HostFS: Synchronize or remove installed unit
    Enabler->>Systemd: Reload systemd units
    Enabler->>Systemd: Disable or stop removed service

    Daemon-->>Daemon: Receive SIGINT or SIGTERM
    Daemon->>Watcher: Stop filesystem watchers
    Daemon-->>Wrapper: Exit
    Wrapper-->>Systemd: Service stopped

    User->>SkupperCLI: system uninstall --force
    SkupperCLI->>Installer: Remove site service enabler
    Installer->>Systemd: Stop and disable helper service
    Installer->>HostFS: Remove wrapper and unit files
    Installer->>Systemd: Reload unit configuration
Loading

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Sequence diagram generated.

Use: "_site-service-enabler",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
slog.Info("Starting site-service-enabler", slog.String("version", version.Version))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Worth checking if another instance is running or not.

}

if reloadType == types.SystemReloadTypeAuto {
enabler := newSiteServiceEnablerInstaller()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Safer to let it just run when not running in a container.
If someone, for example, installs the controller using Ansible (which runs the cli image in a container), then it should not be installed.

After=network.target

[Service]
Type=simple

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.

I thought I heard @hash-d suggest something like this:

What if instead of a long-lived service we had two units: one path unit watching a file and then a service unit with type=oneshot that syncs all unit files, enables/disables skupper services and exits. The system controller would just have to touch the path file on change, and the path unit would kick the service unit.

I think that regardless of which way we go on the skupper CLI dependency this works out better:

  • If we require skupper is installed, which seems fine to me, we don't have some hidden one-off daemon mode in the CLI.
  • If we accommodate installs without the skupper dependency, I think we probably could write this service in bash.

@nluaces
nluaces force-pushed the add-systmd-service-per-router branch from 7c16669 to 3adcc22 Compare August 19, 2026 15:39
@coderabbitai

This comment was marked as spam.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c23868f-5366-4aa7-8c7d-7bb0dfa0f4e0

📥 Commits

Reviewing files that changed from the base of the PR and between 7c16669 and 31a09d7.

📒 Files selected for processing (7)
  • internal/kube/certificates/mgr_test.go
  • internal/nonkube/bootstrap/install.go
  • internal/nonkube/bootstrap/site_service_enabler_installer.go
  • internal/nonkube/bootstrap/site_service_enabler_installer_test.go
  • internal/nonkube/bootstrap/site_service_enabler_script.template
  • internal/nonkube/bootstrap/site_service_enabler_service.template
  • internal/nonkube/bootstrap/uninstall.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread internal/nonkube/bootstrap/install.go
Comment thread internal/nonkube/bootstrap/site_service_enabler_installer_test.go
Comment thread internal/nonkube/bootstrap/site_service_enabler_installer.go Outdated
Comment thread internal/nonkube/bootstrap/site_service_enabler_script.template Outdated
Comment thread internal/nonkube/bootstrap/site_service_enabler_script.template Outdated
Comment thread internal/nonkube/bootstrap/site_service_enabler_script.template

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c90ce887-4a47-4326-8daf-4f044bf0e85c

📥 Commits

Reviewing files that changed from the base of the PR and between 31a09d7 and 2192d0a.

📒 Files selected for processing (5)
  • internal/nonkube/bootstrap/install.go
  • internal/nonkube/bootstrap/site_service_enabler_installer.go
  • internal/nonkube/bootstrap/site_service_enabler_installer_test.go
  • internal/nonkube/bootstrap/site_service_enabler_script.template
  • internal/nonkube/bootstrap/uninstall.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread internal/nonkube/bootstrap/site_service_enabler_installer.go
Comment thread internal/nonkube/bootstrap/site_service_enabler_script.template

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b80af336-1df8-4c46-9646-849bd55c6c68

📥 Commits

Reviewing files that changed from the base of the PR and between 2192d0a and ebe29b8.

📒 Files selected for processing (2)
  • internal/nonkube/bootstrap/site_service_enabler_installer.go
  • internal/nonkube/bootstrap/site_service_enabler_installer_test.go

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment on lines +91 to +101
if _, err := os.Stat(unitFile); err == nil {
if err := s.systemctl("stop", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to stop %s: %w", siteServiceEnablerServiceFile, err)
}
if err := s.systemctl("disable", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to disable %s: %w", siteServiceEnablerServiceFile, err)
}
if err := os.Remove(unitFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove unit file: %w", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return errors that prevent inspection of the unit file.

At Line 91, a permission or I/O error from os.Stat is handled as if the unit were absent. Remove can then return success after removing the wrapper and reloading systemd while the helper unit remains installed. Return errors other than os.IsNotExist(err).

Proposed fix
-	if _, err := os.Stat(unitFile); err == nil {
+	if _, err := os.Stat(unitFile); err == nil {
 		if err := s.systemctl("stop", siteServiceEnablerServiceFile); err != nil {
 			return fmt.Errorf("failed to stop %s: %w", siteServiceEnablerServiceFile, err)
 		}
 		if err := s.systemctl("disable", siteServiceEnablerServiceFile); err != nil {
 			return fmt.Errorf("failed to disable %s: %w", siteServiceEnablerServiceFile, err)
 		}
 		if err := os.Remove(unitFile); err != nil && !os.IsNotExist(err) {
 			return fmt.Errorf("failed to remove unit file: %w", err)
 		}
+	} else if !os.IsNotExist(err) {
+		return fmt.Errorf("failed to inspect unit file: %w", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := os.Stat(unitFile); err == nil {
if err := s.systemctl("stop", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to stop %s: %w", siteServiceEnablerServiceFile, err)
}
if err := s.systemctl("disable", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to disable %s: %w", siteServiceEnablerServiceFile, err)
}
if err := os.Remove(unitFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove unit file: %w", err)
}
}
if _, err := os.Stat(unitFile); err == nil {
if err := s.systemctl("stop", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to stop %s: %w", siteServiceEnablerServiceFile, err)
}
if err := s.systemctl("disable", siteServiceEnablerServiceFile); err != nil {
return fmt.Errorf("failed to disable %s: %w", siteServiceEnablerServiceFile, err)
}
if err := os.Remove(unitFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove unit file: %w", err)
}
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to inspect unit file: %w", err)
}

@nluaces
nluaces requested review from c-kruse and fgiorgetti August 26, 2026 13:26
Comment thread internal/nonkube/bootstrap/install.go Outdated
for _, container := range containers {
if container.Name == containerName {
return true
return container.Running

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.

I think this change ends up changing how other parts of the install work. Not sure which behavior is better - neither of them feel totally correct to me. Maybe we need a function here that returns true when the container is found AND the container state instead of just a basic IsContainerRunning func.

from main:

[christian@rh skupper] (main)$ go run ./cmd/skupper/ system install -p podman
Pulled system-controller image: quay.io/skupper/system-controller:v2-dev
Platform podman is now configured for Skupper
[christian@rh skupper] (main)$ podman stop christian-skupper-controller
christian-skupper-controller
[christian@rh skupper] (main)$ go run ./cmd/skupper/ system install -p podman
Warning: The system controller container "christian-skupper-controller" is already running in Podman.
[christian@rh skupper] (main)$ ./skupper system uninstall -p podman --force
Unable to uninstall.
Error: failed to stop system-controller container: error stopping container christian-skupper-controller:
[christian@rh skupper] (main)$ podman start christian-skupper-controller
christian-skupper-controller
[christian@rh skupper] (main)$ go run ./cmd/skupper/ system uninstall -p podman --force
System-controller has been removed
Platform podman infrastructure for Skupper is now uninstalled

from this PR:

[christian@rh skupper] (add-systmd-service-per-router)$ go run ./cmd/skupper/ system install -p podman
Pulled system-controller image: quay.io/skupper/system-controller:v2-dev
Platform podman is now configured for Skupper
[christian@rh skupper] (add-systmd-service-per-router)$ go run ./cmd/skupper/ system install -p podman
Warning: The system controller container "christian-skupper-controller" is already running in Podman.
[christian@rh skupper] (add-systmd-service-per-router)$ podman stop christian-skupper-controller
christian-skupper-controller
[christian@rh skupper] (add-systmd-service-per-router)$ go run ./cmd/skupper/ system install -p podman
Pulled system-controller image: quay.io/skupper/system-controller:v2-dev
failed to configure the environment : failed to create system-controller container: error creating container christian-skupper-controller: container create: creating container storage: the container name "christian-skupper-controller" is already in use by 15be017357be68663d643f69400d652ecec647be5505eacc72a94f1b4689ac15. You have to remove that container to be able to reuse that name: that name is already in use
exit status 1
[christian@rh skupper] (add-systmd-service-per-router)$ podman start christian-skupper-controller
christian-skupper-controller
[christian@rh skupper] (add-systmd-service-per-router)$ ./skupper system uninstall -p podman --force
System-controller has been removed
Platform podman infrastructure for Skupper is now uninstalled

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/nonkube/bootstrap/uninstall.go (1)

18-18: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Move helper removal after platform-mismatch checks.

At Line 18, Remove() runs before Uninstall checks whether the controller belongs to another engine. If a Podman controller exists and the selected platform is Docker, Lines 31-34 return without removing that controller, but the host-side enabler is already gone. The same occurs for Docker when the selected platform is Podman.

Check both platform mismatches first. Remove the helper only after those checks, while keeping removal before the selected-platform container-absent return.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bade2805-de12-42e6-a4e7-e3aa7b1c12dc

📥 Commits

Reviewing files that changed from the base of the PR and between ebe29b8 and 3aa401b.

📒 Files selected for processing (4)
  • internal/nonkube/bootstrap/install.go
  • internal/nonkube/bootstrap/uninstall.go
  • internal/nonkube/client/compat/container.go
  • pkg/container/client.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable systemd services in the host that were created by the system-controller container

3 participants