diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 0e7a9adb2d..9c9c185261 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -211,13 +211,21 @@ values or credentials: ```bash systemctl show pulse.service \ - --property=User --property=Group --property=NoNewPrivileges \ - --property=PrivateTmp --property=ProtectSystem --property=ProtectHome + --property=LoadState \ + --property=User \ + --property=Group \ + --property=NoNewPrivileges \ + --property=PrivateTmp \ + --property=ProtectSystem \ + --property=ProtectHome ``` Use your actual server unit name if it differs; older installations may use -`pulse-backend.service`. An empty `User=` means systemd runs the service as -root. Missing hardening is not repaired just by downloading a new binary: +`pulse-backend.service`. Interpret these settings only when +`LoadState=loaded`; `LoadState=not-found` means the selected unit is missing, +not that a root service is running. For a loaded service, an empty `User=` +means systemd runs it as root. Missing hardening is not repaired just by +downloading a new binary: **the installer preserves existing service units during updates**, including custom units. diff --git a/frontend-modern/browser-tests/systemd-install-help.cjs b/frontend-modern/browser-tests/systemd-install-help.cjs index bf9592a683..e4cf19a301 100644 --- a/frontend-modern/browser-tests/systemd-install-help.cjs +++ b/frontend-modern/browser-tests/systemd-install-help.cjs @@ -53,7 +53,8 @@ const { chromium, webkit } = require('playwright'); assert.notEqual(await details.getAttribute('open'), null); const content = await details.innerText(); for (const text of ['use the signed installer above', 'User=pulse', 'Group=pulse', - 'NoNewPrivileges=true', 'ProtectSystem=strict', 'An empty User= means', + 'NoNewPrivileges=true', 'ProtectSystem=strict', 'LoadState=loaded', + 'not that a root service is running', 'an empty User=', 'the installer preserves existing service units during updates', 'Do not overwrite a working unit, change only its service user', 'preserve the data directory and its encryption key', @@ -61,6 +62,8 @@ const { chromium, webkit } = require('playwright'); assert.ok(content.includes(text), text); const command = await details.locator('pre').innerText(); assert.ok(command.startsWith('systemctl show pulse.service')); + assert.ok(command.includes('--property=LoadState')); + assert.ok(Math.max(...command.split('\n').map((line) => line.length)) < 34); assert.ok(!/--property=(Environment|ExecStart)|sudo|tee|install -m/.test(command)); assert.ok(!await page.locator('article').innerText().then((text) => text.includes('sudo tee /etc/systemd/system/pulse.service'))); @@ -78,7 +81,8 @@ const { chromium, webkit } = require('playwright'); assert.deepEqual(errors, []); results.push({ engine, version: browser.version(), width, keyboardDisclosure: true, nonRootServer: true, agentBoundary: true, - readOnlyCredentialSafeCommand: true, preservationWarning: true, + readOnlyCredentialSafeCommand: true, loadedUnitRequired: true, phoneCommandFits: true, + preservationWarning: true, noDocumentOverflow: true, keyboardRecoveryLink: true, errors }); await browser.close(); browser = undefined; diff --git a/frontend-modern/public/docs/INSTALL.md b/frontend-modern/public/docs/INSTALL.md index 0e7a9adb2d..9c9c185261 100644 --- a/frontend-modern/public/docs/INSTALL.md +++ b/frontend-modern/public/docs/INSTALL.md @@ -211,13 +211,21 @@ values or credentials: ```bash systemctl show pulse.service \ - --property=User --property=Group --property=NoNewPrivileges \ - --property=PrivateTmp --property=ProtectSystem --property=ProtectHome + --property=LoadState \ + --property=User \ + --property=Group \ + --property=NoNewPrivileges \ + --property=PrivateTmp \ + --property=ProtectSystem \ + --property=ProtectHome ``` Use your actual server unit name if it differs; older installations may use -`pulse-backend.service`. An empty `User=` means systemd runs the service as -root. Missing hardening is not repaired just by downloading a new binary: +`pulse-backend.service`. Interpret these settings only when +`LoadState=loaded`; `LoadState=not-found` means the selected unit is missing, +not that a root service is running. For a loaded service, an empty `User=` +means systemd runs it as root. Missing hardening is not repaired just by +downloading a new binary: **the installer preserves existing service units during updates**, including custom units. diff --git a/frontend-modern/src/utils/__tests__/docsLinks.test.ts b/frontend-modern/src/utils/__tests__/docsLinks.test.ts index 255b77334b..497dd069d2 100644 --- a/frontend-modern/src/utils/__tests__/docsLinks.test.ts +++ b/frontend-modern/src/utils/__tests__/docsLinks.test.ts @@ -69,7 +69,9 @@ describe('docsLinks', () => { expect(custom).toContain('use the signed installer above'); expect(custom).toContain('`User=pulse`, `Group=pulse`'); expect(custom).toContain('`ProtectSystem=strict` and `ProtectHome=true`'); - expect(custom).toContain('An empty `User=` means'); + expect(custom).toContain('`LoadState=loaded`'); + expect(custom).toContain('not that a root service is running'); + expect(custom).toContain('an empty `User=`'); expect(custom).toContain('the installer preserves existing service units during updates'); expect(custom).toContain('Do not overwrite a working unit, change only its service user'); expect(custom).toContain('preserve the data directory and its encryption'); @@ -77,6 +79,8 @@ describe('docsLinks', () => { const commands = [...custom.matchAll(/```bash\n([\s\S]*?)```/g)].map((match) => match[1]); expect(commands).toHaveLength(1); expect(commands[0]).toContain('systemctl show pulse.service'); + expect(commands[0]).toContain('--property=LoadState'); + expect(Math.max(...commands[0].split('\n').map((line) => line.length))).toBeLessThan(34); expect(commands[0]).not.toMatch(/--property=(Environment|ExecStart)|sudo|tee|install -m/); expect(installation).not.toContain('sudo tee /etc/systemd/system/pulse.service'); expect(installation).not.toContain('ExecStart=/usr/local/bin/pulse');