Skip to content

Commit b5949a1

Browse files
Reidondclaude
andauthored
Add macOS support via platform abstraction layer (#6)
* feat: add macOS terminal support via platform abstraction layer Introduce a platform abstraction layer (bwssh.platform) that encapsulates all OS-specific behavior behind common interfaces, enabling macOS support with zero breaking changes to existing Linux functionality. Changes across all 5 platform areas: - Process credentials: LOCAL_PEERCRED/LOCAL_PEERPID on macOS (vs SO_PEERCRED) - Process metadata: libproc/sysctl via ctypes on macOS (vs /proc filesystem) - Authorization: socket-permission-based auth on macOS (vs D-Bus polkit) - Service management: launchd user agents on macOS (vs systemd units) - Sleep/wake events: NSWorkspace notifications on macOS (vs logind D-Bus) Also adds: - Conditional dbus-fast dependency (Linux-only via sys_platform marker) - Optional pyobjc-framework-Cocoa extra for macOS sleep watcher - launchd plist template and --launchd install flag - Platform-aware runtime directory resolution - Comprehensive platform test suite (14 pass, 5 macOS-only skip on Linux) All 487 existing tests continue to pass unchanged. https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz * docs: add macOS terminal support implementation plan https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz * fix: resolve CI failures for macOS platform port - Remove plan.md from repository - Fix ruff formatting on _darwin.py and _linux.py - Add sys_platform marker to macos-sleep extra so pyobjc is only required on macOS (fixes --all-extras on Linux) https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz * fix: resolve mypy errors in _darwin.py - Cast struct.unpack result to int to fix no-any-return - Remove unused type: ignore comments on AppKit import and SleepObserver https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz * fix: remove unused I001 noqa directive in _darwin.py https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz * docs: add macOS installation and setup instructions - Update introduction to cover both Linux and macOS - Add macOS setup section to installation guide (launchd, SSH_AUTH_SOCK, sleep-lock extra, platform notes) - Update CLI commands reference with --launchd flag - Add macOS examples for SSH_AUTH_SOCK and launchd - Add macOS authorization notes to security guide https://claude.ai/code/session_0163PhXY1cy3hnDYDPiTf3Fz --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 903d3b5 commit b5949a1

File tree

16 files changed

+1097
-192
lines changed

16 files changed

+1097
-192
lines changed

docs/content/1.getting-started/1.introduction.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,26 @@ title: "Introduction"
33
description: "What bwssh does and when to use it."
44
---
55

6-
bwssh is a Linux-first SSH agent that signs using keys stored in Bitwarden.
6+
bwssh is an SSH agent that signs using keys stored in Bitwarden.
77
It exposes a Unix-domain socket compatible with OpenSSH tools such as `ssh`
8-
and `git`, while gating signing requests through polkit authorization.
8+
and `git`, while gating signing requests through polkit authorization (Linux)
9+
or socket permissions (macOS).
910

1011
## Use bwssh when
1112

1213
- You store SSH keys in Bitwarden and want agent-style signing.
13-
- You want per-request or per-connection approval prompts.
14-
- You want a user-level daemon managed by systemd.
14+
- You want per-request or per-connection approval prompts (Linux with polkit).
15+
- You want a user-level daemon managed by systemd (Linux) or launchd (macOS).
1516

1617
## Requirements
1718

18-
- Linux with systemd user services.
19-
- Python 3.12+.
20-
- Bitwarden CLI (`bw`) installed and logged in.
19+
- **Linux**: systemd user services, Python 3.12+, Bitwarden CLI (`bw`).
20+
- **macOS**: Python 3.12+, Bitwarden CLI (`bw`).
21+
22+
### Optional dependencies
23+
24+
- **Linux polkit**: `dbus-fast` (installed automatically) for per-request authorization prompts.
25+
- **macOS sleep-lock**: `pyobjc-framework-Cocoa` (install with `pip install bwssh[macos-sleep]`) to auto-lock the agent when your Mac sleeps.
2126

2227
::note
2328
This documentation includes AI integration with an MCP server and automatic

docs/content/1.getting-started/2.installation.md

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,95 @@ uv sync
1515
uv run bwssh --version
1616
```
1717

18-
## Install systemd assets
18+
## Prepare Bitwarden
19+
20+
Make sure the Bitwarden CLI is available:
1921

2022
```bash
21-
uv run bwssh install --user-systemd
23+
bw --version
2224
```
2325

24-
## Optional: install polkit policy
26+
Log in and unlock as needed:
2527

2628
```bash
27-
uv run bwssh install --polkit | sudo tee /etc/polkit-1/actions/io.github.reidond.bwssh.policy
29+
bw login
30+
bw unlock --raw
2831
```
2932

30-
## Prepare Bitwarden
33+
## Linux setup
3134

32-
Make sure the Bitwarden CLI is available:
35+
### Install systemd units
3336

3437
```bash
35-
bw --version
38+
bwssh install --user-systemd
39+
systemctl --user daemon-reload
40+
systemctl --user enable --now bwssh-agent.socket
3641
```
3742

38-
Log in and unlock as needed:
43+
### Optional: install polkit policy
3944

4045
```bash
41-
bw login
42-
bw unlock --raw
46+
bwssh install --polkit | sudo tee /usr/share/polkit-1/actions/io.github.reidond.bwssh.policy > /dev/null
47+
```
48+
49+
### Point SSH at the agent socket
50+
51+
```bash
52+
export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/bwssh/agent.sock"
53+
```
54+
55+
Add the line above to your `~/.bashrc` or `~/.zshrc` to make it permanent.
56+
57+
## macOS setup
58+
59+
### Install the launchd agent
60+
61+
```bash
62+
bwssh install --launchd
4363
```
64+
65+
This writes a plist to `~/Library/LaunchAgents/io.github.reidond.bwssh-agent.plist`
66+
and prints the `launchctl load` command to start it.
67+
68+
### Load the agent
69+
70+
```bash
71+
launchctl load ~/Library/LaunchAgents/io.github.reidond.bwssh-agent.plist
72+
```
73+
74+
The agent will start automatically on login from now on.
75+
76+
### Point SSH at the agent socket
77+
78+
```bash
79+
export SSH_AUTH_SOCK="${TMPDIR}bwssh/agent.sock"
80+
```
81+
82+
Add the line above to your `~/.zshrc` (or `~/.bashrc`) to make it permanent.
83+
84+
### Optional: enable sleep-lock
85+
86+
Install the `macos-sleep` extra so the agent locks automatically when your Mac
87+
goes to sleep:
88+
89+
```bash
90+
uv pip install 'bwssh[macos-sleep]'
91+
```
92+
93+
### Unlock and test
94+
95+
```bash
96+
bwssh unlock
97+
ssh -T git@github.com
98+
```
99+
100+
## macOS notes
101+
102+
- **No polkit**: macOS does not have D-Bus or polkit. Authorization relies on
103+
Unix socket permissions — only processes running as your user can connect to
104+
the agent socket (mode `0600`). The `require_polkit` config option is ignored
105+
on macOS with a warning logged.
106+
- **No tray icon**: The system tray (`bwssh tray`) requires AppIndicator3 and
107+
is Linux-only for now.
108+
- **Logs**: Daemon stdout/stderr go to `/tmp/bwssh-agent.stdout.log` and
109+
`/tmp/bwssh-agent.stderr.log` by default (configured in the plist).

docs/content/2.cli/2.commands.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ bwssh start
2121
bwssh stop
2222
```
2323

24-
## Install systemd or polkit assets
24+
## Install service integration
2525

2626
```bash
27+
# Linux: install systemd user units
2728
bwssh install --user-systemd
29+
30+
# macOS: install launchd user agent
31+
bwssh install --launchd
32+
33+
# Linux: print polkit policy to stdout
2834
bwssh install --polkit
2935
```
3036

docs/content/2.cli/3.examples.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,30 @@ bwssh unlock
1313
## Use with SSH
1414

1515
```bash
16+
# Linux
1617
export SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/bwssh/agent.sock
18+
19+
# macOS
20+
export SSH_AUTH_SOCK=${TMPDIR}bwssh/agent.sock
21+
1722
ssh -T git@github.com
1823
```
1924

20-
## Install and enable systemd socket
25+
## Linux: install and enable systemd socket
2126

2227
```bash
2328
bwssh install --user-systemd
2429
systemctl --user daemon-reload
2530
systemctl --user enable --now bwssh-agent.socket
2631
```
2732

33+
## macOS: install and load launchd agent
34+
35+
```bash
36+
bwssh install --launchd
37+
launchctl load ~/Library/LaunchAgents/io.github.reidond.bwssh-agent.plist
38+
```
39+
2840
## Inspect loaded keys
2941

3042
```bash

docs/content/3.guide/1.configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "Configuration"
33
description: "Configure sockets, Bitwarden, auth, and SSH behavior."
44
---
55

6-
Config file location:
6+
Config file location (same on Linux and macOS):
77

88
```
99
${XDG_CONFIG_HOME:-~/.config}/bwssh/config.toml

docs/content/3.guide/2.security.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ description: "Authorization, polkit, and forwarding policy."
55

66
## Authorization Overview
77

8-
bwssh supports two authorization modes:
8+
### Linux
9+
10+
bwssh supports two authorization modes on Linux:
911

1012
1. **Disabled (default)**: All signing requests from local processes are allowed without prompts. This is suitable for personal workstations.
1113

1214
2. **Polkit-enabled**: Each signing request triggers a desktop prompt asking you to approve. Useful for shared machines or extra security.
1315

16+
### macOS
17+
18+
On macOS, authorization relies on Unix socket permissions. The agent socket is
19+
created with mode `0600`, so only processes running as your user can connect.
20+
There is no polkit equivalent — the `require_polkit` config option is ignored
21+
on macOS (a warning is logged if set to `true`).
22+
1423
## Default Mode (No Prompts)
1524

1625
By default, bwssh allows all signing requests from local processes. This means:

pyproject.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
[project]
44
name = "bwssh"
55
version = "0.1.3"
6-
description = "Bitwarden-backed SSH agent for Linux"
6+
description = "Bitwarden-backed SSH agent"
77
readme = "README.md"
88
requires-python = ">=3.12"
99
dependencies = [
1010
"click>=8.3.1",
1111
"cryptography>=46.0.4",
12-
"dbus-fast>=4.0.0",
12+
"dbus-fast>=4.0.0; sys_platform == 'linux'",
1313
"textual>=7.5.0",
1414
]
1515

1616
[project.optional-dependencies]
1717
gui = ["PyGObject>=3.42.0,<3.50"]
18+
macos-sleep = ["pyobjc-framework-Cocoa>=10.0; sys_platform == 'darwin'"]
1819

1920
[project.scripts]
2021
bwssh = "bwssh.cli:main"
@@ -85,6 +86,14 @@ ignore_missing_imports = true
8586
module = "gi.*"
8687
ignore_missing_imports = true
8788

89+
[[tool.mypy.overrides]]
90+
module = "Foundation.*"
91+
ignore_missing_imports = true
92+
93+
[[tool.mypy.overrides]]
94+
module = "AppKit.*"
95+
ignore_missing_imports = true
96+
8897
[tool.pytest.ini_options]
8998
testpaths = ["tests"]
9099
pythonpath = ["src"]

0 commit comments

Comments
 (0)