# Platform-Specific Code Differences Between PAI on macOS and Linux

> Discover platform specific code differences between PAI on macOS and Linux. Learn about utility syntax, service management, and binary variations for seamless cross-platform development.

- Repository: [Daniel Miessler 🛡️/Personal_AI_Infrastructure](https://github.com/danielmiessler/personal_ai_infrastructure)
- Tags: internals
- Published: 2026-02-16

---

**PAI implements conditional code paths that detect the host operating system at runtime to handle BSD versus GNU utility syntax, LaunchAgent versus systemd service management, and platform-specific binaries like `afplay` versus `mpv`.**

PAI (Personal AI Infrastructure) by Daniel Miessler was originally architected for macOS and later extended to support Linux through a series of platform-aware abstractions. Understanding these platform-specific code differences between PAI on macOS and Linux is essential for contributors deploying the voice server or observability components on heterogeneous environments.

## Shell Syntax and Path Conventions

PAI shell scripts handle incompatible syntax between BSD and GNU utilities, particularly for in-place file editing and package manager paths.

**`sed -i` syntax differences** represent the most common friction point. macOS uses BSD `sed`, which requires an empty string argument for the backup suffix, while Linux uses GNU `sed`, which does not. According to the source analysis in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 25-28), PAI implements a runtime check:

```bash
if [ "$(uname -s)" = "Darwin" ]; then
    sed -i '' 's/old/new/g' file.txt
else
    sed -i 's/old/new/g' file.txt
fi

```

**Homebrew path handling** also differs. On macOS Apple Silicon, PAI references `/opt/homebrew/bin` as the default Homebrew location. The Linux implementation checks for this directory first and falls back to the system `$PATH` if it does not exist, as documented in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 30-34) and implemented in [`pai-observability-server/manage.sh`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/pai-observability-server/manage.sh).

## Service Management Architecture

PAI uses entirely different service managers for background processes, conditional on the host platform.

**macOS LaunchAgent implementation** relies on `launchctl` and plist files stored under `~/Library/LaunchAgents`. The installer creates a plist defining the PAI voice server environment variables and scheduling, then loads it via `launchctl load`.

**Linux systemd user service** provides equivalent functionality using a unit file stored in `~/.config/systemd/user`. The installation script writes the service definition and executes `systemctl --user enable pai.service` followed by `systemctl --user start pai.service`.

Both paths are documented in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 36-49) and implemented conditionally in `Voice system/INSTALL.md`.

## Audio and Notification Subsystems

Platform-specific binaries require abstraction layers for media playback and desktop notifications.

**Audio playback** uses `afplay` on macOS, which is a built-in command-line player. On Linux, PAI implements a cascading fallback chain: it detects and uses the first available player in the order `mpg123` → `mpv` → `snap/mpv`. If none are found, it logs a warning and continues without audio. This logic appears in the voice server TypeScript source and is documented in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 62-70).

**Desktop notifications** use `osascript` to trigger Notification Center alerts on macOS. The Linux branch uses `notify-send` from the `libnotify` package. Both implementations are guarded by `process.platform` checks in the TypeScript runtime, as shown in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 71-74).

## Runtime Platform Detection Patterns

PAI implements consistent detection patterns across shell scripts and TypeScript code.

**Shell-based detection** uses `uname -s` to set an `OS_TYPE` variable at the start of management scripts:

```bash
OS_TYPE="$(uname -s)"
if [ "$OS_TYPE" = "Darwin" ]; then
    # macOS-specific section

elif [ "$OS_TYPE" = "Linux" ]; then
    # Linux-specific section

else
    echo "Unsupported platform: $OS_TYPE"
    exit 1
fi

```

This pattern appears in [`pai-observability-server/manage.sh`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/pai-observability-server/manage.sh) and is recommended in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 38-44).

**TypeScript/Bun detection** relies on the Node.js `process.platform` global:

```typescript
if (process.platform === 'darwin') {
  // macOS: use afplay for audio
  exec('afplay', [audioFile]);
} else if (process.platform === 'linux') {
  // Linux: pick the first available player
  const player = which('mpg123') ?? which('mpv') ?? which('snap')?.includes('mpv') ? 'mpv' : null;
  if (player) exec(player, [audioFile]);
  else console.warn('No audio player found – install mpg123 or mpv');
}

```

This implementation is documented in [`PLATFORM.md`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/PLATFORM.md) (lines 53-60) and implemented in the voice server source.

## Summary

- **Shell utility syntax** differs between BSD (macOS) and GNU (Linux) tools, requiring runtime checks for commands like `sed -i`.
- **Service management** uses `launchctl` and LaunchAgents on macOS versus `systemd` user units on Linux, with conditional installation logic in the setup scripts.
- **Audio and notifications** require platform-specific binaries: `afplay` and `osascript` on macOS, versus `mpg123`/`mpv` and `notify-send` on Linux.
- **Runtime detection** consistently uses `uname -s` in shell scripts and `process.platform` in TypeScript to branch execution paths.
- **Path conventions** follow OS standards: `~/Library` on macOS versus `~/.config` on Linux for logs and service definitions.

## Frequently Asked Questions

### How does PAI detect which platform it's running on?

PAI uses two primary detection mechanisms. In shell scripts, it reads the `OS_TYPE` variable from `uname -s`, checking for "Darwin" or "Linux". In TypeScript code running under Bun, it evaluates `process.platform === 'darwin'` or `process.platform === 'linux'`. These checks appear at the entry points of platform-specific operations in [`pai-observability-server/manage.sh`](https://github.com/danielmiessler/Personal_AI_Infrastructure/blob/main/pai-observability-server/manage.sh) and the voice server source.

### What audio player does PAI use on Linux if multiple are installed?

PAI implements a cascading priority chain on Linux: it first attempts to use `mpg123`, then falls back to `mpv`, and finally checks for `snap/mpv`. The first binary found in the system PATH is selected. If none are available, PAI logs a warning and continues execution without audio playback rather than failing. This logic is implemented in the voice server's TypeScript runtime.

### Why does PAI use different service managers on macOS and Linux?

PAI adheres to each operating system's native service management conventions to ensure proper integration with user sessions and system startup. macOS uses **LaunchAgents** controlled by `launchctl`, which is the standard for user-level background processes on Darwin systems. Linux uses **systemd user services**, which provide equivalent functionality while following the predominant Linux service management standard. The installer scripts in `Voice system/INSTALL.md` conditionally generate the appropriate unit files for each platform.

### Can I run PAI on Windows?

The current PAI codebase does not implement Windows-specific code paths. While the TypeScript `process.platform` check could theoretically be extended to handle `win32`, the shell scripts rely heavily on Unix-specific utilities like `sed`, `uname`, and `launchctl`/`systemd` that have no direct Windows equivalents. Users wishing to run PAI on Windows would need to use the Windows Subsystem for Linux (WSL2) to provide a Linux-compatible environment.