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 }