detect if running as a service on mac and linux, to prevent automatic update
Massimo Melina committed
Mar 15, 2026 at 22:26 UTC
7feb716e000a3ce3356c497c833d00e883288aa3
1 file changed
+30
-4
src/util-os.ts
+30
-4
@@ -5,8 +5,8 @@ import { exists, isWindowsDrive, onlyTruthy, promiseBestEffort } from './misc'
5
import Parser from '@gregoranders/csv';
6
import { pid, ppid } from 'node:process'
7
import { promisify } from 'util'
8
-import { IS_WINDOWS } from './const'
9
-import { statfs } from 'node:fs/promises'
8
+import { IS_MAC, IS_WINDOWS } from './const'
9
+import { readFile, statfs } from 'node:fs/promises'
10
11
const DF_TIMEOUT = 2000
12
@@ -79,16 +79,42 @@ async function getWindowsServicePids() {
79
return Object.fromEntries(parsed.slice(2).filter(x => x[2] !== no).map(x => [x[1], x[2]]))
80
}
81
82
-export const RUNNING_AS_SERVICE = IS_WINDOWS && getWindowsServicePids().then(x => {
83
- const ret = x[pid] || x[ppid]
82
+export const RUNNING_AS_SERVICE = detectRunningAsService().then(ret => {
83
if (ret)
84
console.log("running as service", ret)
85
return ret
86
}, e => {
87
console.log("couldn't determine if we are running as a service")
88
console.debug(e)
89
+ return false
90
})
91
92
+async function detectRunningAsService() {
93
+ if (IS_WINDOWS)
94
+ return getWindowsServicePids().then(x => x[pid] || x[ppid])
95
+ if (process.platform === 'linux') {
96
+ // interactive shells live in session-*.scope; only a leaf *.service means this process is actually inside a service unit
97
+ const systemdFromCgroup = await readFile('/proc/self/cgroup', 'utf8').then(cgroup =>
98
+ cgroup.split('\n')
99
+ // cgroup v1 uses single-colon fields while v2 uses double-colon; taking the last colon-separated field covers both
100
+ .map(line => line.split(':').pop()?.split('/').pop() || '')
101
+ .find(unit => unit.endsWith('.service')))
102
+ if (systemdFromCgroup)
103
+ return systemdFromCgroup
104
+ // keep env markers as a last fallback only for non-interactive runs, to avoid false positives in normal terminal sessions
105
+ if (!process.stdin.isTTY && !process.stdout.isTTY && (process.env.INVOCATION_ID || process.env.JOURNAL_STREAM || process.env.NOTIFY_SOCKET || process.env.SYSTEMD_EXEC_PID))
106
+ return process.env.INVOCATION_ID || process.env.SYSTEMD_EXEC_PID || 'systemd'
107
+ return false
108
+ }
109
+ // launchd services expose LAUNCH_JOB_NAME and run detached from interactive ttys
110
+ if (!IS_MAC || process.stdin.isTTY || process.stdout.isTTY)
111
+ return false
112
+ const launchdService = process.env.LAUNCH_JOB_NAME || false
113
+ if (launchdService)
114
+ return launchdService
115
+ return false
116
+}
117
+
118
export function reg(...pars: string[]) {
119
return promisify(execFile)('reg', pars).then(x => x.stdout)
120
}