# D-Bus Conversion Technical Notes (2025-12-15)

## Issue Reference
https://github.com/onetimesecret/ots-containers/issues/1

## Problem Context

The `systemd.py` module parses `systemctl` CLI output, which caused a production bug:
- `discover_instances()` returned failed units as "running"
- Root cause: didn't parse ACTIVE/SUB state columns, only matched unit names
- Fixed in commit 614099e but parsing remains fragile

## D-Bus vs CLI

systemd's official API is D-Bus. CLI tools are wrappers. Key advantages:
- Structured data (properties, not text)
- Stable API across versions
- Better testability (mock D-Bus interface)
- Proper async job handling for start/stop operations

## Implementation Notes

### Library Choice

**Recommend pystemd**:
- Facebook/Meta maintained
- Direct systemd bindings
- Used in production at scale
- `pip install pystemd` (requires libsystemd-dev)

### Key D-Bus Concepts

Manager object: `/org/freedesktop/systemd1`
- `ListUnits()` - Returns array of unit info tuples
- `StartUnit(name, mode)` - Returns job path
- `StopUnit(name, mode)` - Returns job path
- `Reload()` - Daemon reload

Unit properties (org.freedesktop.systemd1.Unit):
- `ActiveState`: "active" | "inactive" | "failed" | "activating" | "deactivating"
- `SubState`: "running" | "dead" | "failed" | "exited" | etc.
- `LoadState`: "loaded" | "not-found" | "masked"

### Migration Strategy

1. Add pystemd dependency
2. Create `_dbus.py` module with D-Bus implementation
3. Update `systemd.py` to use D-Bus by default
4. Keep CLI fallback for environments without D-Bus access
5. Update tests to mock D-Bus interface

### Test Considerations

pystemd can be mocked:
```python
@pytest.fixture
def mock_manager(mocker):
    manager = mocker.Mock()
    manager.list_units.return_value = [
        (b"onetime@7043.service", b"", b"loaded", b"active", b"running", ...),
    ]
    mocker.patch("pystemd.systemd1.Manager", return_value=manager)
    return manager
```

### Async Operations

Start/Stop/Restart are async in systemd. Current code uses `check=True` which waits.
D-Bus returns a job path; need to either:
- Poll job status until complete
- Use synchronous convenience methods if available
- Accept fire-and-forget for some operations

## Related Commits

- 614099e: Filter discover_instances to only return running units (the parsing fix)
- 5078405: Fix container naming (another verification failure)

## Branch Context

Work on `delano/next` branch. Issue #1 created for tracking.
