Skip to content

Conversation

@iotux
Copy link
Contributor

@iotux iotux commented Dec 13, 2025

❗ Important Announcements

Click here for more details:

⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we don’t waste your time. Before submitting, make sure you have read our pull request guidelines: Pull Request Rules

🚫 Please Avoid Unnecessary Pinging of Maintainers

We kindly ask you to refrain from pinging maintainers unless absolutely necessary. Pings are for critical/urgent pull requests that require immediate attention.

📋 Overview

  • What problem does this pull request address?

    • Currently, Uptime Kuma lacks a generic way to monitor local services or custom scripts on the host machine. While there are specific monitors for databases, Docker, etc., there is no general-purpose monitor to check the status of other services (like systemd services) or the output of custom health check scripts. This has been a requested feature in the community (e.g., in issue Monitor for Linux systemd services #1677), but previous proposals for dedicated monitors (like a systemd monitor) were turned down due to concerns about complexity and "clutter".
    • This PR aims to address this need by introducing a flexible and simple monitor type that doesn't add significant complexity to the UI.
  • What features or functionality does this pull request introduce or enhance?

  1. New 'Local Service' Monitor Type:

    • Adds a new monitor type that can execute any shell command on the host machine.
    • The monitor's status is determined by checking if the command's output contains an "Expected Output" string. If the expected output is left empty, the monitor's status is determined by the command's exit code (0 for "Up", non-zero for "Down").
    • This allows for a wide range of monitoring scenarios, from checking systemd services to running custom scripts or even checking remote services via ssh.
  2. Bug Fix for Prometheus Errors:

    • This PR also fixes a bug where adding a new tag to any monitor would cause Prometheus to throw an Error: Added label "X" is not included in initial labelset. The fix re-initializes Prometheus metrics when a new tag is added, so a server restart is no longer required.
  3. Bug Fix for Tag UI:

    • A minor bug where the UI was not updated immediately after adding/deleting a tag has also been fixed. This bug was discovered during development. It has later been reported as "Tags is not saved if you dont save 2 time" Tags is not saved if you dont save 2 time #6476

🛠️ Type of change

  • 🐛 Bugfix (a non-breaking change that resolves an issue)
  • ✨ New feature (a non-breaking change that adds new functionality)
  • ⚠️ Breaking change (a fix or feature that alters existing functionality in a way that could cause issues)
  • 🎨 User Interface (UI) updates // Added page for Local Service Monitor
  • 📄 New Documentation (addition of new documentation)
  • 📄 Documentation Update (modification of existing documentation)
  • 📄 Documentation Update Required (the change requires updates to related documentation)
  • 🔧 Other (please specify):

Disclaimer

  • This development has been done entirely by the Gemini CLI LLM, patience and carefully crafted prompts by an 80 year old developer, who has shaking hands and a brain that thinks that the current task is better left to a professional that doesn't ask for food or other unreasonable requirements.

📄 Checklist

  • 🔍 My code adheres to the style guidelines of this project.
  • 🦿 I have indicated where (if any) I used an LLM for the contributions
  • ✅ I ran ESLint and other code linters for modified files.
  • 🛠️ I have reviewed and tested my code.
  • 📝 I have commented my code, especially in hard-to-understand areas (e.g., using JSDoc for methods).
  • ⚠️ My changes generate no new warnings.
  • 🤖 My code needed automated testing. I have added them (this is an optional task).
  • 📄 Documentation updates are included (if applicable).
  • 🔒 I have considered potential security impacts and mitigated risks.
  • 🧰 Dependency updates are listed and explained.
  • 📚 I have read and understood the Pull Request guidelines.

📷 Screenshots or Visual Changes

  • UI Modifications: Highlight any changes made to the user interface.
  • Before & After: Include screenshots or comparisons (if applicable).
Event Before After
UP Before After
DOWN Before After
Certificate-expiry Before After
Testing Before After
Screenshot_20251212_102126 image image

@louislam
Copy link
Owner

  1. New 'Local Service' Monitor Type

Need discussion on how to design it properly, directly execute a command, will end up becoming the recent Next.js RCE issue.

  1. Bug Fix for Prometheus Errors:
  2. Bug Fix for Tag UI:

Please create 2 & 3 in separate pull requests.

@louislam louislam marked this pull request as draft December 13, 2025 08:07
@iotux iotux force-pushed the feature/local-service-monitor branch from 07f47b6 to d76ce4e Compare December 14, 2025 15:39
Copy link
Collaborator

@CommanderStorm CommanderStorm left a comment

Choose a reason for hiding this comment

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

overall, looks like a promising start.
There is some unrelated code which needs moving to other PRs, but that should be easy to remove.

I have two requests:

  • could you add a very simple testcase that only executes in CI, so we all are sure that this code works in the future (even if new functionality gets added)
  • could you make this a non-docker-only monitor (calling it in docker does not quite make senese). I think tailscale was similar.
  • I think this will only work on linux right, so we need to also restrict to this.

// systemctl is-active exits with 0 if the service is active,
// and a non-zero code if it is inactive, failed, or not found.
if (error) {
heartbeat.status = DOWN;
Copy link
Collaborator

Choose a reason for hiding this comment

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

otherwise retrys don't work

Suggested change
heartbeat.status = DOWN;

Comment on lines 19 to 20
// This is the name of the service to check e.g. "nginx.service"
const serviceName = monitor.local_service_name;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: please inline serviceName, as this way I think the code is more clear.

* @returns {Promise<void>}
*/
static async init() {
PrometheusClient.register.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

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

does not seem related

Suggested change
PrometheusClient.register.clear();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. This looks like a leftover from my local debugging to prevent duplicate registration errors during hot-reloads. It is definitely not needed for production and I will remove it

server/server.js Outdated
Comment on lines 1177 to 1178
await Prometheus.init();

Copy link
Collaborator

Choose a reason for hiding this comment

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

does not seem related

Suggested change
await Prometheus.init();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. This was likely leftover debug code intended to force a metric refresh, but it is too aggressive/destructive for production as it clears the global register. I have removed it

Comment on lines 1306 to 1307
await server.sendUpdateMonitorIntoList(socket, monitorID);

Copy link
Collaborator

Choose a reason for hiding this comment

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

does not seem related

Suggested change
await server.sendUpdateMonitorIntoList(socket, monitorID);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

</option>

<option value="local-service">
{{ $t("Local Service") }}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please name the monitor Systemd Service instead as it is a bit unclear what a local service is otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am seriously considering to extend this monitor to support Windows Services as well (using PowerShell's 'Get-Service'). Because of this hybrid support, 'Systemd Service' would be too specific. I believe that 'Local Service' or 'System Service' fits best as it implies checking the underlying OS service manager (systemd for Linux, SCM for Windows).

Copy link
Collaborator

Choose a reason for hiding this comment

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

"System service" if you add windows support, otherwise "Systemd service" 👍🏻

<label for="local-service-name" class="form-label">{{ $t("Service Name") }}</label>
<input id="local-service-name" v-model="monitor.local_service_name" type="text" class="form-control" required placeholder="nginx.service">
<div class="form-text">
{{ $t("localServiceDescription") }}
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets add here (via i18n-t) how to debug this monitor.
This way it is much clearer what this monitor does.

@CommanderStorm CommanderStorm added the pr:please address review comments this PR needs a bit more work to be mergable label Dec 14, 2025
@louislam
Copy link
Owner

I am not so familiar with Linux distros, but I have seen some Linux distros don't have systemctl/systemd.

May need a better name instead of "Local Service".

@iotux
Copy link
Contributor Author

iotux commented Dec 15, 2025

I am not so familiar with Linux distros, but I have seen some Linux distros don't have systemctl/systemd.
May need a better name instead of "Local Service".

Systemd is used by Ubuntu, Debian, CentOS, RedHat, Fedora, SUSE to name the most known. Those systems covers the vast majority of the server OS market.

To keep the PR clean and maintainable, I propose we stick to Systemd for Linux and later PowerShell for Windows.

Chosing 'Local Service' or 'System Service' is a matter or taste. My personal preference would be 'System Service'.

Comment on lines 31 to 42
// 1. Capture the raw output (prioritize stderr for errors)
let output = (stderr || stdout || "").toString().trim();

// 2. Truncate if too long to ~200 chars
if (output.length > 200) {
output = output.substring(0, 200) + "...";
}

if (error) {
// stderr often contains useful info like "service not found"
// Use the truncated output, or a default fallback if empty
heartbeat.msg = stderr || stdout || `Service '${monitor.local_service_name}' is not running.`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// 1. Capture the raw output (prioritize stderr for errors)
let output = (stderr || stdout || "").toString().trim();
// 2. Truncate if too long to ~200 chars
if (output.length > 200) {
output = output.substring(0, 200) + "...";
}
if (error) {
// stderr often contains useful info like "service not found"
// Use the truncated output, or a default fallback if empty
heartbeat.msg = stderr || stdout || `Service '${monitor.local_service_name}' is not running.`;
// stderr often contains useful info like "service not found"
let output = (stderr || stdout || "").toString().trim();
if (output.length > 200) {
output = output.substring(0, 200) + "...";
}
if (error) {
heartbeat.msg = output || `Service '${monitor.local_service_name}' is not running.`;

@iotux iotux marked this pull request as ready for review December 15, 2025 14:34
@iotux
Copy link
Contributor Author

iotux commented Dec 15, 2025

PR Update Notice
Update: Windows Support & Renaming

Based on the feedback and the cross-platform nature of this feature, I have expanded the scope and finalized the implementation:

Renamed to "System Service": Changed the monitor name from "Local Service" to "System Service" to be OS-agnostic (supports both systemd and Windows Service Manager).

Added Windows Support: The monitor now auto-detects the OS. On Windows, it uses powershell (via Get-Service) to check status, while maintaining systemctl for Linux.

Added debug instructions with platform-specific commands.

Verification:

Verified manually on Linux

I don't have Windows. Hence, I need someone to voluntarily test on a Windows platform

@iotux iotux closed this Dec 15, 2025
@iotux iotux reopened this Dec 15, 2025
@CommanderStorm CommanderStorm added looking-for-testers and removed pr:please address review comments this PR needs a bit more work to be mergable labels Dec 15, 2025
@CommanderStorm
Copy link
Collaborator

Would writing a testcase for this be possible?
I also don't have windows, but if there were a test I am fine with not manually testing this.

@CommanderStorm CommanderStorm changed the title Feature/local service monitor & issue #6476 fix feat: system service (aka systemd/ windows service) monitor Dec 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tags is not saved if you dont save 2 time

3 participants