Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,22 @@ omarchy plugin add https://github.com/Githubguy132010/MarchyBar --enable

The first public release is **v0.1.0**. See [releases](https://github.com/Githubguy132010/MarchyBar/releases) for changes.

Click **▰** in the Omarchy bar. The first launch builds a small native renderer in your user cache; subsequent launches reuse it. Open **Device → Set up Touch Bar**, authenticate the one-time helper installation, and enable the bar. An existing `tiny-dfr` or `touchbard` process must be stopped before enabling MarchyBar.
Click **▰** in the Omarchy bar. The first launch builds a small native renderer in your user cache; subsequent launches reuse it. Install the trusted system package described below, then open **Device → Set up Touch Bar**, authenticate the device configuration, and enable the bar. An existing `tiny-dfr` or `touchbard` process must be stopped before enabling MarchyBar.

The plugin's standard Omarchy installation never executes a privileged hook. The separate setup installs exactly:
### Install the protected system helper

The plugin cannot bootstrap privileged code from its user-writable checkout. An administrator must first install a matching, independently trusted `marchybar-system` package. No published signed binary package is provided yet. For a local build, obtain and review a separate source snapshot, review `packaging/PKGBUILD` and its inputs, then build as an ordinary user:

```sh
cd packaging
makepkg
```

Have the administrator install the reviewed package with `pacman -U /path/to/marchybar-system-0.1.0-1-any.pkg.tar.zst`. Do not use the live plugin checkout as a trusted source or run its installer with sudo/pkexec. Local checksums bind the reviewed inputs; they do not authenticate an untrusted download or protect a compromised build session.

The package installs root-owned files under `/usr/lib/marchybar-system/` and a dedicated policy at `/usr/share/polkit-1/actions/org.marchybar.system.policy`. Package installation does not start the service. Setup accepts only `setup` or `remove`, checks the helper's canonical path, ownership, mode and SHA-256 before calling `/usr/bin/pkexec`, and requires fresh administrator authentication for an active session. The protected helper verifies its fixed payload hashes and never reads code from the plugin checkout. Python runs in isolated mode and the privileged shell receives a fixed environment.

The plugin's standard Omarchy installation never executes a privileged hook. The separate setup deploys exactly:

- `/usr/local/lib/marchybar/device-broker.py`
- `/etc/systemd/system/marchybar-device.service`
Expand Down Expand Up @@ -105,7 +118,7 @@ Use **Device → Update MarchyBar** or `marchybar update` for repository updates

When the plugin revision changes, MarchyBar restarts the shell to load the new code. An enabled Touch Bar returns after the session unlocks; an intentionally disabled bar stays disabled. If the shell cannot restart, the updater reports the pending restart and the command to run after unlocking. A failed plugin update or restart stops the combined workflow. Omarchy may restart the shell again or offer a reboot. Updates run directly through `omarchy plugin update` still need a shell restart to replace MarchyBar's retained service.

If a release changes the device helper, run **Set up Touch Bar** again. Updates never install privileged helper files automatically. The source, preset, and control protocol versions are explicit.
If a release changes the device helper, have the administrator install the matching reviewed system package, then run **Set up Touch Bar** again. Mismatched helper hashes fail closed. Updates never install privileged helper files automatically. The source, preset, and control protocol versions are explicit.

## Disable or uninstall

Expand All @@ -117,6 +130,8 @@ marchybar uninstall-system
omarchy plugin remove marchybar.touchbar --yes
```

After `uninstall-system` succeeds, the administrator may remove `marchybar-system` with the package manager. Keep the package installed until device cleanup succeeds.

User presets are deliberately retained. Remove `~/.config/marchybar` separately only if you want to erase them.

## Development
Expand All @@ -126,6 +141,7 @@ npm ci --ignore-scripts
npm run build
npm test
python tests/broker_test.py
python tests/system_action_test.py
tests/test-qml.sh
```

Expand Down
4 changes: 2 additions & 2 deletions bin/marchybar
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ case "$action" in
daemon) prepare; exec node "$MARCHYBAR_ROOT/backend/server.mjs" "$@" ;;
preview) prepare; exec node "$MARCHYBAR_ROOT/backend/server.mjs" --preview "$@" ;;
open) exec omarchy-shell shell summon marchybar.touchbar '{}' ;;
setup) exec pkexec /bin/bash "$MARCHYBAR_ROOT/packaging/install-system.sh" ;;
setup) exec /usr/bin/python3 -I "$MARCHYBAR_ROOT/bin/system-action.py" setup "$@" ;;
update) exec /bin/bash "$MARCHYBAR_ROOT/bin/update.sh" "$@" ;;
uninstall-system) exec pkexec /bin/bash "$MARCHYBAR_ROOT/packaging/install-system.sh" --remove ;;
uninstall-system) exec /usr/bin/python3 -I "$MARCHYBAR_ROOT/bin/system-action.py" remove "$@" ;;
help|--help|-h)
cat <<'HELP'
MarchyBar — native Omarchy Touch Bar configuration
Expand Down
34 changes: 34 additions & 0 deletions bin/system-action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/python3 -I
"""Check the exact installed helper before requesting its dedicated Polkit action."""
import hashlib
import os
from pathlib import Path
import stat
import sys

HELPER = Path('/usr/lib/marchybar-system/setup-helper')
EXPECTED_SHA256 = 'a998950fa104fb601643665fde7cda963e6a8ea87379ac26a98e9c8c9c8c33b6'

def main():
if sys.argv[1:] not in [['setup'], ['remove']]:
raise ValueError('Expected setup or remove, with no other arguments')
for item in [*reversed(HELPER.parents), HELPER]:
info = item.lstat()
if stat.S_ISLNK(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise ValueError(f'Untrusted helper path: {item}')
if item != HELPER and not stat.S_ISDIR(info.st_mode):
raise ValueError(f'Not a directory: {item}')
if HELPER.resolve(strict=True) != HELPER or not stat.S_ISREG(info.st_mode) or not info.st_mode & 0o111:
raise ValueError('Invalid helper executable')
if hashlib.sha256(HELPER.read_bytes()).hexdigest() != EXPECTED_SHA256:
raise ValueError('Installed helper version/integrity does not match this plugin')
# All ancestors and the executable are protected from session-user replacement.
os.execve('/usr/bin/pkexec', ['/usr/bin/pkexec', str(HELPER), sys.argv[1]],
{'PATH': '/usr/bin:/bin', 'LANG': 'C'})

if __name__ == '__main__':
try:
main()
except (OSError, ValueError) as error:
print(f'MarchyBar setup refused: {error}. Install the matching trusted system package; see README.md.', file=sys.stderr)
sys.exit(1)
17 changes: 17 additions & 0 deletions packaging/PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build as an ordinary user from a separately reviewed, trusted source snapshot.
# No install hook: installing the package does not enable the device service.
pkgname=marchybar-system
pkgver=0.1.0
pkgrel=1
pkgdesc='Root-owned, integrity-bound MarchyBar device setup helper'
arch=('any')
license=('GPL-3.0-or-later')
depends=('python' 'python-gobject' 'bash' 'polkit' 'systemd' 'acl')
source=('install-system.sh' 'device-broker.py' 'marchybar-device.service' '90-marchybar.rules' 'setup-helper' 'org.marchybar.system.policy')
sha256sums=('4a70f10820534a6aeb3aa5f79bb180d683f5cde23a4223d50e24d7e2f134873a' '7addc1e2c37aa098622b96f5b0afa4b0189ff54b538cad50b77b59d99ead839e' 'f5a2a3401bf6edffd6d1623606e741dbe8967a1f10420d0ec2540678ac8b16cd' '42b0e793f1f17b4cb992e2348fcac16dca353b76faad452a0348bb02e440ee78' 'a998950fa104fb601643665fde7cda963e6a8ea87379ac26a98e9c8c9c8c33b6' '29d6dcf145f75bfe310ad565410d1b2f56139287f007931e588c4afc7f5b5da8')
package() {
install -dm755 "$pkgdir/usr/lib/marchybar-system"
install -m755 setup-helper "$pkgdir/usr/lib/marchybar-system/setup-helper"
install -m644 install-system.sh device-broker.py marchybar-device.service 90-marchybar.rules "$pkgdir/usr/lib/marchybar-system/"
install -Dm644 org.marchybar.system.policy "$pkgdir/usr/share/polkit-1/actions/org.marchybar.system.policy"
}
4 changes: 3 additions & 1 deletion packaging/install-system.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
# Installs only the device-specific helper. The renderer always runs as the user.
set -euo pipefail
[[ $EUID == 0 ]] || { echo 'Run marchybar setup from the editor or command line.' >&2; exit 1; }
MARCHYBAR_PACKAGE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
export PATH=/usr/bin:/bin
MARCHYBAR_PACKAGE=/usr/lib/marchybar-system
[[ $# == 0 || ( $# == 1 && $1 == --remove ) ]] || { echo 'Unknown setup argument' >&2; exit 1; }
if [[ ${1:-} == --remove ]]; then
helper_state() {
local properties key value
Expand Down
14 changes: 14 additions & 0 deletions packaging/org.marchybar.system.policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="org.marchybar.system.configure">
<description>Configure the MarchyBar Touch Bar device service</description>
<message>Authentication is required to configure the MarchyBar device service</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/lib/marchybar-system/setup-helper</annotate>
</action>
</policyconfig>
58 changes: 58 additions & 0 deletions packaging/setup-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/python3 -I
"""Fixed-scope system setup; deployed only through the administrator's package manager."""
import hashlib
import os
from pathlib import Path
import stat
import sys

ROOT = Path('/usr/lib/marchybar-system')
PAYLOAD = {'install-system.sh': '4a70f10820534a6aeb3aa5f79bb180d683f5cde23a4223d50e24d7e2f134873a', 'device-broker.py': '7addc1e2c37aa098622b96f5b0afa4b0189ff54b538cad50b77b59d99ead839e', 'marchybar-device.service': 'f5a2a3401bf6edffd6d1623606e741dbe8967a1f10420d0ec2540678ac8b16cd', '90-marchybar.rules': '42b0e793f1f17b4cb992e2348fcac16dca353b76faad452a0348bb02e440ee78'}

def trusted(path, executable=False):
path = Path(path)
for item in [*reversed(path.parents), path]:
info = item.lstat()
if stat.S_ISLNK(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise ValueError(f'Untrusted system path: {item}')
if item != path and not stat.S_ISDIR(info.st_mode):
raise ValueError(f'Not a directory: {item}')
if not stat.S_ISREG(info.st_mode) or (executable and not info.st_mode & 0o111):
raise ValueError(f'Invalid system file: {path}')
return path

def main():
if os.geteuid() != 0 or sys.argv[1:] not in [['setup'], ['remove']]:
raise ValueError('Expected authenticated setup or remove, with no other arguments')
if Path(__file__) != ROOT / 'setup-helper':
raise ValueError('Helper must be installed by the system package manager')
trusted(__file__, executable=True)
for name, digest in PAYLOAD.items():
file = trusted(ROOT / name)
if hashlib.sha256(file.read_bytes()).hexdigest() != digest:
raise ValueError(f'Helper payload integrity mismatch: {name}')
trusted('/usr/bin/bash', executable=True)
# Fail closed on writable/symlinked destination ancestors or existing files.
for name in ['/usr/local/lib/marchybar/device-broker.py',
'/etc/systemd/system/marchybar-device.service',
'/etc/udev/rules.d/90-marchybar.rules']:
file = Path(name)
for item in [*reversed(file.parents), file]:
if not item.exists() and not item.is_symlink():
continue
info = item.lstat()
if stat.S_ISLNK(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise ValueError(f'Untrusted destination: {item}')
os.chdir('/')
os.umask(0o077)
args = ['/usr/bin/bash', '--noprofile', '--norc', str(ROOT / 'install-system.sh')]
if sys.argv[1] == 'remove':
args.append('--remove')
os.execve(args[0], args, {'PATH': '/usr/bin:/bin', 'LANG': 'C'})

if __name__ == '__main__':
try:
main()
except (OSError, ValueError) as error:
print(f'MarchyBar setup refused: {error}', file=sys.stderr)
sys.exit(1)
1 change: 1 addition & 0 deletions tests/broker_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import unittest
from unittest.mock import patch
from setup_test import SetupTest
from system_action_test import SystemActionTest

spec = importlib.util.spec_from_file_location('broker', Path(__file__).parents[1] / 'packaging/device-broker.py')
broker = importlib.util.module_from_spec(spec)
Expand Down
98 changes: 98 additions & 0 deletions tests/system_action_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Hardware-free privilege boundary regression tests; never invoke real pkexec."""
import hashlib
import importlib.machinery
import importlib.util
from pathlib import Path
import shutil
import stat
import subprocess
import tempfile
import types
import unittest
from unittest.mock import patch

ROOT = Path(__file__).parents[1]

def load(name, path):
loader = importlib.machinery.SourceFileLoader(name, str(path))
spec = importlib.util.spec_from_loader(name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module

class SystemActionTest(unittest.TestCase):
def test_installer_replacement_cannot_execute_privileged_code(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'bin').mkdir()
(root / 'packaging').mkdir()
for name in ['marchybar', 'system-action.py']:
shutil.copy(ROOT / 'bin' / name, root / 'bin' / name)
marker = root / 'executed'
(root / 'packaging/install-system.sh').write_text(f'#!/bin/bash\ntouch {marker}\n')
module = load('launcher', root / 'bin/system-action.py')
# Fixture is deliberately user-writable, regardless of the test runner UID.
helper = root / 'replacement'
helper.write_text('untrusted')
helper.chmod(0o777)
module.HELPER = helper
with patch.object(module.os, 'execve') as execute, patch.object(module.sys, 'argv', ['launcher', 'setup']):
with self.assertRaises(ValueError):
module.main()
execute.assert_not_called()
# The actual CLI must refuse ordinary setup/removal with a missing helper,
# even when the checkout installer has been replaced by executable code.
launcher = root / 'bin/system-action.py'
launcher.write_text(launcher.read_text().replace(
"Path('/usr/lib/marchybar-system/setup-helper')",
"Path('/nonexistent/marchybar-system/setup-helper')"))
for action in ['setup', 'uninstall-system']:
result = subprocess.run(['/bin/bash', str(root / 'bin/marchybar'), action], capture_output=True)
self.assertNotEqual(result.returncode, 0)
self.assertIn(b'MarchyBar setup refused', result.stderr)
# Extra arguments must also be rejected before escalation.
for action in ['setup', 'uninstall-system']:
result = subprocess.run(['/bin/bash', str(root / 'bin/marchybar'), action, '--invalid'], capture_output=True)
self.assertNotEqual(result.returncode, 0)
self.assertFalse(marker.exists())

def test_exact_helper_checks_and_arguments(self):
module = load('launcher_checks', ROOT / 'bin/system-action.py')
with tempfile.TemporaryDirectory() as directory:
helper = Path(directory) / 'helper'
helper.write_bytes(b'fixture')
module.HELPER = helper
module.EXPECTED_SHA256 = hashlib.sha256(b'fixture').hexdigest()
def metadata(path):
mode = stat.S_IFREG | 0o755 if path == helper else stat.S_IFDIR | 0o755
return types.SimpleNamespace(st_uid=0, st_mode=mode)
with patch.object(Path, 'lstat', metadata), patch.object(module.sys, 'argv', ['launcher', 'setup']), patch.object(module.os, 'execve') as execute:
module.main()
self.assertEqual(execute.call_args.args[1], ['/usr/bin/pkexec', str(helper), 'setup'])
execute.reset_mock()
helper.write_bytes(b'replaced')
with self.assertRaises(ValueError): module.main()
execute.assert_not_called()
for mode, uid in [(stat.S_IFREG | 0o777, 0), (stat.S_IFREG | 0o755, 1000), (stat.S_IFLNK | 0o777, 0)]:
def bad(path): return types.SimpleNamespace(st_uid=uid, st_mode=mode)
with patch.object(Path, 'lstat', bad), patch.object(module.sys, 'argv', ['launcher', 'setup']), patch.object(module.os, 'execve') as execute:
with self.assertRaises(ValueError): module.main()
execute.assert_not_called()
for args in [[], ['setup', 'extra'], ['remove', '/tmp/file'], ['other']]:
with patch.object(module.sys, 'argv', ['launcher', *args]), patch.object(module.os, 'execve') as execute:
with self.assertRaises(ValueError): module.main()
execute.assert_not_called()

def test_payload_and_launcher_integrity_binding(self):
helper = load('helper', ROOT / 'packaging/setup-helper')
launcher = load('launcher_integrity', ROOT / 'bin/system-action.py')
self.assertEqual(launcher.EXPECTED_SHA256, hashlib.sha256((ROOT / 'packaging/setup-helper').read_bytes()).hexdigest())
for name, digest in helper.PAYLOAD.items():
self.assertEqual(digest, hashlib.sha256((ROOT / 'packaging' / name).read_bytes()).hexdigest(), name)
for args in [[], ['setup', 'extra'], ['remove', 'extra'], ['other']]:
with patch.object(helper.sys, 'argv', ['helper', *args]), patch.object(helper.os, 'execve') as execute:
with self.assertRaises(ValueError): helper.main()
execute.assert_not_called()

if __name__ == '__main__':
unittest.main()
Loading