From 67510128465d63172f51edd1d5b494eed85c0a8c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:24:46 +0000
Subject: [PATCH 1/6] Initial plan
From a4422f772d315e1544e813e85211f91d58a01fe1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:29:43 +0000
Subject: [PATCH 2/6] Add launchd service configuration and orchestrator daemon
support
Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
---
LAUNCHD_SERVICE_GUIDE.md | 426 ++++++++++++++++++++++++
com.privaseeai.security.plist | 64 ++++
src/privaseeai_security/daemon.py | 90 +++++
src/privaseeai_security/orchestrator.py | 57 ++++
4 files changed, 637 insertions(+)
create mode 100644 LAUNCHD_SERVICE_GUIDE.md
create mode 100644 com.privaseeai.security.plist
create mode 100644 src/privaseeai_security/daemon.py
diff --git a/LAUNCHD_SERVICE_GUIDE.md b/LAUNCHD_SERVICE_GUIDE.md
new file mode 100644
index 0000000..23ef300
--- /dev/null
+++ b/LAUNCHD_SERVICE_GUIDE.md
@@ -0,0 +1,426 @@
+# PrivaseeAI Security - launchd Service Installation Guide
+
+This guide explains how to install, configure, and manage the PrivaseeAI Security background service using macOS launchd.
+
+## Overview
+
+The `com.privaseeai.security.plist` file configures PrivaseeAI Security to run as a persistent background service that:
+
+- ✅ Runs `python -m privaseeai_security.orchestrator` automatically
+- ✅ Starts on boot (user agent)
+- ✅ Auto-restarts if crashed (with exponential backoff via ThrottleInterval)
+- ✅ Logs to `/var/log/privaseeai/security.log`
+- ✅ Runs from `/opt/privaseeai` working directory
+- ✅ Executes as the current user
+- ✅ Prevents rapid restart loops with 60-second throttle
+
+## Prerequisites
+
+1. **Python 3.11+** installed at `/usr/bin/python3`
+2. **PrivaseeAI Security** installed as a Python package
+3. **Proper permissions** to write to `/var/log/privaseeai/`
+
+## Installation Steps
+
+### 1. Install PrivaseeAI Security Package
+
+First, ensure the package is installed in your Python environment:
+
+```bash
+# From the repository root
+pip install -e .
+
+# Verify installation
+python3 -m privaseeai_security --version
+```
+
+### 2. Create Required Directories
+
+Create the log directory with proper permissions:
+
+```bash
+# Create log directory
+sudo mkdir -p /var/log/privaseeai
+
+# Set ownership to current user
+sudo chown $(whoami):staff /var/log/privaseeai
+
+# Set appropriate permissions
+chmod 755 /var/log/privaseeai
+```
+
+### 3. Set Up Working Directory
+
+Create and configure the working directory:
+
+```bash
+# Create working directory
+sudo mkdir -p /opt/privaseeai
+
+# Set ownership to current user
+sudo chown $(whoami):staff /opt/privaseeai
+
+# Copy or link your configuration files
+# (Optional - adjust based on your setup)
+# cp config.yaml /opt/privaseeai/
+```
+
+### 4. Configure Environment Variables (Optional)
+
+If you need Telegram alerts, set environment variables in the plist or create a configuration file:
+
+```bash
+# Option 1: Edit the plist file to add your tokens
+# Edit com.privaseeai.security.plist and add to EnvironmentVariables:
+# TELEGRAM_BOT_TOKEN
+# your_bot_token_here
+# TELEGRAM_CHAT_ID
+# your_chat_id_here
+
+# Option 2: Use a .env file in /opt/privaseeai/
+cat > /opt/privaseeai/.env << 'EOF'
+TELEGRAM_BOT_TOKEN=your_bot_token_here
+TELEGRAM_CHAT_ID=your_chat_id_here
+EOF
+chmod 600 /opt/privaseeai/.env
+```
+
+### 5. Install the launchd Service
+
+Copy the plist file to the user's LaunchAgents directory:
+
+```bash
+# Copy plist to LaunchAgents
+cp com.privaseeai.security.plist ~/Library/LaunchAgents/
+
+# Verify the file is in place
+ls -l ~/Library/LaunchAgents/com.privaseeai.security.plist
+```
+
+## Loading and Managing the Service
+
+### Load the Service
+
+Start the service immediately and enable auto-start on boot:
+
+```bash
+# Load and start the service
+launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# Alternative: Bootstrap (recommended on macOS 11+)
+launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.privaseeai.security.plist
+```
+
+### Check Service Status
+
+Verify the service is running:
+
+```bash
+# List loaded services and check if ours is running
+launchctl list | grep com.privaseeai.security
+
+# Get detailed status
+launchctl print gui/$(id -u)/com.privaseeai.security
+```
+
+Expected output for running service:
+```
+12345 0 com.privaseeai.security
+```
+(PID, exit code, label)
+
+### View Service Logs
+
+Monitor the service logs in real-time:
+
+```bash
+# Tail the log file
+tail -f /var/log/privaseeai/security.log
+
+# View last 100 lines
+tail -n 100 /var/log/privaseeai/security.log
+
+# Search for errors
+grep -i error /var/log/privaseeai/security.log
+```
+
+### Restart the Service
+
+If you need to restart the service:
+
+```bash
+# Stop the service
+launchctl stop com.privaseeai.security
+
+# Start the service (it will auto-restart due to KeepAlive)
+launchctl start com.privaseeai.security
+
+# Or unload and reload
+launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+```
+
+### Stop and Unload the Service
+
+To completely stop and disable the service:
+
+```bash
+# Unload the service (stops it and disables auto-start)
+launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# Alternative: Bootout (recommended on macOS 11+)
+launchctl bootout gui/$(id -u)/com.privaseeai.security
+```
+
+## Testing the Service
+
+### Basic Functionality Test
+
+1. **Load the service:**
+ ```bash
+ launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+ ```
+
+2. **Check if it's running:**
+ ```bash
+ launchctl list | grep com.privaseeai.security
+ ps aux | grep "privaseeai_security.orchestrator"
+ ```
+
+3. **Verify logs are being written:**
+ ```bash
+ tail -f /var/log/privaseeai/security.log
+ ```
+
+ You should see startup messages like:
+ ```
+ INFO - Starting PrivaseeAI Security Orchestrator daemon...
+ INFO - ✅ Orchestrator daemon started
+ ```
+
+### Crash Recovery Test
+
+Test the auto-restart functionality:
+
+1. **Find the service PID:**
+ ```bash
+ launchctl list | grep com.privaseeai.security
+ # Note the PID (first column)
+ ```
+
+2. **Kill the process:**
+ ```bash
+ kill -9
+ ```
+
+3. **Wait 60 seconds (ThrottleInterval), then check:**
+ ```bash
+ launchctl list | grep com.privaseeai.security
+ ```
+
+ The service should have a new PID, indicating it restarted automatically.
+
+4. **Check logs for restart:**
+ ```bash
+ grep -A 5 "Orchestrator daemon started" /var/log/privaseeai/security.log | tail -20
+ ```
+
+### Boot Persistence Test
+
+Test auto-start on boot:
+
+1. **Reboot your system**
+2. **After login, check service status:**
+ ```bash
+ launchctl list | grep com.privaseeai.security
+ tail /var/log/privaseeai/security.log
+ ```
+
+### Manual Invocation Test
+
+Test running the orchestrator manually (without launchd):
+
+```bash
+# Run directly
+cd /opt/privaseeai
+python3 -m privaseeai_security.orchestrator
+
+# Should start and show logs in terminal
+# Press Ctrl+C to stop
+```
+
+## Troubleshooting
+
+### Service Won't Start
+
+1. **Check Python installation:**
+ ```bash
+ which python3
+ /usr/bin/python3 --version # Should be 3.11+
+ ```
+
+2. **Verify package installation:**
+ ```bash
+ python3 -c "import privaseeai_security; print(privaseeai_security.__version__)"
+ ```
+
+3. **Check permissions:**
+ ```bash
+ ls -la /var/log/privaseeai/
+ ls -la /opt/privaseeai/
+ ```
+
+4. **View system logs:**
+ ```bash
+ log show --predicate 'process == "launchd"' --last 5m | grep privaseeai
+ ```
+
+### Service Crashes Immediately
+
+1. **Check error logs:**
+ ```bash
+ tail -50 /var/log/privaseeai/security.log
+ ```
+
+2. **Test manual execution:**
+ ```bash
+ cd /opt/privaseeai
+ python3 -m privaseeai_security.orchestrator
+ ```
+
+3. **Check for missing dependencies:**
+ ```bash
+ python3 -c "from privaseeai_security.orchestrator import ThreatOrchestrator"
+ ```
+
+### Logs Not Appearing
+
+1. **Verify log directory permissions:**
+ ```bash
+ ls -la /var/log/privaseeai/
+ ```
+
+2. **Check if process can write:**
+ ```bash
+ sudo -u $(whoami) touch /var/log/privaseeai/test.log
+ rm /var/log/privaseeai/test.log
+ ```
+
+3. **Ensure PYTHONUNBUFFERED is set** (already in plist)
+
+## Configuration Reference
+
+### Key launchd Properties
+
+- **Label**: `com.privaseeai.security` - Unique service identifier
+- **RunAtLoad**: `true` - Start on boot/load
+- **KeepAlive**: Configured to restart on any exit
+- **ThrottleInterval**: `60` seconds - Prevents rapid restart loops
+- **WorkingDirectory**: `/opt/privaseeai` - Where service runs from
+- **StandardOutPath/StandardErrorPath**: `/var/log/privaseeai/security.log` - Log location
+- **LimitLoadToSessionType**: `Aqua` - User agent (not system daemon)
+
+### Environment Variables
+
+The plist includes:
+- `PATH`: Standard system PATH
+- `PYTHONUNBUFFERED`: `1` - Force unbuffered output for immediate logs
+
+## Advanced Configuration
+
+### Custom Python Interpreter
+
+If you use a virtual environment:
+
+1. **Edit the plist file:**
+ ```xml
+ ProgramArguments
+
+ /path/to/your/venv/bin/python3
+ -m
+ privaseeai_security.orchestrator
+
+ ```
+
+2. **Reload the service:**
+ ```bash
+ launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+ launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+ ```
+
+### Custom Backup Path
+
+Modify the plist to pass backup path as environment variable:
+
+```xml
+EnvironmentVariables
+
+
+ PRIVASEE_BACKUP_PATH
+ /path/to/ios/backups
+
+```
+
+Then update orchestrator.py to read this environment variable.
+
+### Adjust Throttle Interval
+
+Change crash recovery timing:
+
+```xml
+ThrottleInterval
+120
+```
+
+## Uninstallation
+
+To completely remove the service:
+
+```bash
+# 1. Unload the service
+launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# 2. Remove the plist file
+rm ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# 3. (Optional) Remove log files
+sudo rm -rf /var/log/privaseeai/
+
+# 4. (Optional) Remove working directory
+sudo rm -rf /opt/privaseeai/
+```
+
+## Security Considerations
+
+1. **Never store secrets in the plist file** - Use environment files or macOS Keychain
+2. **Limit log file permissions** - Only the service user should read/write logs
+3. **Regularly rotate logs** - Use `newsyslog` or similar tools
+4. **Monitor for unusual behavior** - Check logs regularly for errors
+
+## Support
+
+For issues or questions:
+- Check logs: `/var/log/privaseeai/security.log`
+- GitHub Issues: https://github.com/aurelianware/PrivaseeAI.Security/issues
+- Documentation: See README.md and USER_GUIDE.md
+
+## Quick Reference Card
+
+```bash
+# Install
+cp com.privaseeai.security.plist ~/Library/LaunchAgents/
+launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# Status
+launchctl list | grep com.privaseeai.security
+
+# Logs
+tail -f /var/log/privaseeai/security.log
+
+# Restart
+launchctl stop com.privaseeai.security
+
+# Uninstall
+launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+rm ~/Library/LaunchAgents/com.privaseeai.security.plist
+```
diff --git a/com.privaseeai.security.plist b/com.privaseeai.security.plist
new file mode 100644
index 0000000..fc8d121
--- /dev/null
+++ b/com.privaseeai.security.plist
@@ -0,0 +1,64 @@
+
+
+
+
+
+ Label
+ com.privaseeai.security
+
+
+ ProgramArguments
+
+ /usr/bin/python3
+ -m
+ privaseeai_security.orchestrator
+
+
+
+ WorkingDirectory
+ /opt/privaseeai
+
+
+ RunAtLoad
+
+
+
+ KeepAlive
+
+
+ SuccessfulExit
+
+
+
+
+
+ ThrottleInterval
+ 60
+
+
+ StandardOutPath
+ /var/log/privaseeai/security.log
+
+ StandardErrorPath
+ /var/log/privaseeai/security.log
+
+
+ EnvironmentVariables
+
+ PATH
+ /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+ PYTHONUNBUFFERED
+ 1
+
+
+
+ Nice
+ 0
+
+
+ LimitLoadToSessionType
+
+ Aqua
+
+
+
diff --git a/src/privaseeai_security/daemon.py b/src/privaseeai_security/daemon.py
new file mode 100644
index 0000000..7eb165f
--- /dev/null
+++ b/src/privaseeai_security/daemon.py
@@ -0,0 +1,90 @@
+"""Daemon entry point for PrivaseeAI Security orchestrator.
+
+This module provides a standalone entry point for running the orchestrator
+as a background service via launchd or other init systems.
+
+Usage:
+ python -m privaseeai_security.daemon
+"""
+
+import asyncio
+import signal
+import sys
+from pathlib import Path
+from typing import Optional
+
+from .orchestrator import ThreatOrchestrator
+from .logger import get_logger
+
+
+logger = get_logger(__name__)
+
+
+# Global orchestrator instance for signal handling
+_orchestrator: Optional[ThreatOrchestrator] = None
+_shutdown_event = asyncio.Event()
+
+
+def signal_handler(signum, frame):
+ """Handle shutdown signals gracefully."""
+ logger.info(f"Received signal {signum}, initiating shutdown...")
+ _shutdown_event.set()
+
+
+async def run_daemon():
+ """Run the threat orchestrator as a daemon service."""
+ global _orchestrator
+
+ logger.info("Starting PrivaseeAI Security daemon...")
+
+ # Create orchestrator instance
+ _orchestrator = ThreatOrchestrator(
+ backup_path=None, # Auto-detect
+ telegram_enabled=True,
+ monitor_interval=30,
+ scan_backups_on_start=True
+ )
+
+ try:
+ # Start monitoring
+ await _orchestrator.start()
+ logger.info("✅ Orchestrator started successfully")
+
+ # Wait for shutdown signal
+ await _shutdown_event.wait()
+
+ except Exception as e:
+ logger.error(f"Daemon error: {e}", exc_info=True)
+ raise
+ finally:
+ # Clean shutdown
+ if _orchestrator:
+ logger.info("Stopping orchestrator...")
+ await _orchestrator.stop()
+ logger.info("✅ Orchestrator stopped cleanly")
+
+
+def main():
+ """Main entry point for daemon."""
+ # Setup signal handlers
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGINT, signal_handler)
+
+ # Log startup
+ logger.info("PrivaseeAI Security Daemon starting...")
+
+ try:
+ # Run the async daemon
+ asyncio.run(run_daemon())
+ except KeyboardInterrupt:
+ logger.info("Daemon interrupted by user")
+ except Exception as e:
+ logger.error(f"Fatal error: {e}", exc_info=True)
+ sys.exit(1)
+
+ logger.info("Daemon shutdown complete")
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/privaseeai_security/orchestrator.py b/src/privaseeai_security/orchestrator.py
index 6a4f600..832d7d8 100644
--- a/src/privaseeai_security/orchestrator.py
+++ b/src/privaseeai_security/orchestrator.py
@@ -372,3 +372,60 @@ async def scan_now(self) -> ThreatSummary:
logger.info("Manual scan triggered")
await self._scan_backups_once()
return self.get_threat_summary()
+
+
+# Daemon entry point when running as module
+async def _run_daemon():
+ """Run orchestrator as a daemon service."""
+ import signal
+
+ shutdown_event = asyncio.Event()
+ orchestrator = None
+
+ def signal_handler(signum, frame):
+ """Handle shutdown signals."""
+ logger.info(f"Received signal {signum}, shutting down...")
+ shutdown_event.set()
+
+ # Setup signal handlers
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGINT, signal_handler)
+
+ try:
+ # Create and start orchestrator
+ orchestrator = ThreatOrchestrator(
+ backup_path=None, # Auto-detect
+ telegram_enabled=True,
+ monitor_interval=30,
+ scan_backups_on_start=True
+ )
+
+ await orchestrator.start()
+ logger.info("✅ Orchestrator daemon started")
+
+ # Wait for shutdown signal
+ await shutdown_event.wait()
+
+ except Exception as e:
+ logger.error(f"Orchestrator daemon error: {e}", exc_info=True)
+ raise
+ finally:
+ if orchestrator:
+ await orchestrator.stop()
+ logger.info("✅ Orchestrator daemon stopped")
+
+
+if __name__ == "__main__":
+ """Entry point for python -m privaseeai_security.orchestrator"""
+ import sys
+
+ logger.info("Starting PrivaseeAI Security Orchestrator daemon...")
+
+ try:
+ asyncio.run(_run_daemon())
+ except KeyboardInterrupt:
+ logger.info("Daemon interrupted")
+ except Exception as e:
+ logger.error(f"Fatal error: {e}", exc_info=True)
+ sys.exit(1)
+
From 3ca3a78dbb6b4169eaacf0b600804bc6218fe7ba Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:31:48 +0000
Subject: [PATCH 3/6] Add comprehensive tests for launchd service configuration
Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
---
tests/unit/test_launchd_service.py | 137 +++++++++++++++++++++++++++++
1 file changed, 137 insertions(+)
create mode 100644 tests/unit/test_launchd_service.py
diff --git a/tests/unit/test_launchd_service.py b/tests/unit/test_launchd_service.py
new file mode 100644
index 0000000..20158bb
--- /dev/null
+++ b/tests/unit/test_launchd_service.py
@@ -0,0 +1,137 @@
+"""Test launchd service and daemon functionality."""
+
+import subprocess
+import sys
+from pathlib import Path
+import pytest
+
+
+# Get repository root
+REPO_ROOT = Path(__file__).parent.parent.parent
+
+
+def test_plist_file_exists():
+ """Test that the plist file exists."""
+ plist_path = REPO_ROOT / "com.privaseeai.security.plist"
+ assert plist_path.exists(), "com.privaseeai.security.plist not found"
+
+
+def test_plist_is_valid_xml():
+ """Test that the plist file is valid XML."""
+ import xml.etree.ElementTree as ET
+
+ plist_path = REPO_ROOT / "com.privaseeai.security.plist"
+ tree = ET.parse(plist_path)
+ root = tree.getroot()
+
+ assert root.tag == "plist", "Root element should be plist"
+ assert root.attrib.get("version") == "1.0", "plist version should be 1.0"
+
+
+def test_plist_has_required_keys():
+ """Test that the plist has all required launchd keys."""
+ import xml.etree.ElementTree as ET
+
+ plist_path = REPO_ROOT / "com.privaseeai.security.plist"
+ tree = ET.parse(plist_path)
+
+ # Check for required keys
+ required_keys = [
+ "Label",
+ "ProgramArguments",
+ "RunAtLoad",
+ "KeepAlive",
+ "ThrottleInterval",
+ "StandardOutPath",
+ "StandardErrorPath",
+ "WorkingDirectory",
+ ]
+
+ plist_text = ET.tostring(tree.getroot(), encoding='unicode')
+
+ for key in required_keys:
+ assert f"{key}" in plist_text, f"Missing required key: {key}"
+
+
+def test_plist_program_arguments():
+ """Test that ProgramArguments is correctly configured."""
+ import xml.etree.ElementTree as ET
+
+ plist_path = REPO_ROOT / "com.privaseeai.security.plist"
+ tree = ET.parse(plist_path)
+
+ plist_text = ET.tostring(tree.getroot(), encoding='unicode')
+
+ # Should use python3 -m privaseeai_security.orchestrator
+ assert "python3" in plist_text, "Should use python3"
+ assert "privaseeai_security.orchestrator" in plist_text, "Should run orchestrator module"
+
+
+def test_orchestrator_has_main_block():
+ """Test that orchestrator.py has __main__ block for daemon mode."""
+ from privaseeai_security import orchestrator
+
+ # Check that the module has the main block
+ import inspect
+ source = inspect.getsource(orchestrator)
+
+ assert 'if __name__ == "__main__"' in source, "orchestrator.py should have __main__ block"
+ assert '_run_daemon' in source, "orchestrator.py should have _run_daemon function"
+
+
+def test_orchestrator_can_be_imported():
+ """Test that the orchestrator module can be imported."""
+ from privaseeai_security.orchestrator import ThreatOrchestrator
+
+ assert ThreatOrchestrator is not None, "Should be able to import ThreatOrchestrator"
+
+
+def test_daemon_module_exists():
+ """Test that daemon.py module exists as alternative."""
+ from privaseeai_security import daemon
+
+ assert daemon is not None, "daemon module should exist"
+ assert hasattr(daemon, 'main'), "daemon should have main function"
+
+
+def test_launchd_guide_exists():
+ """Test that the LAUNCHD_SERVICE_GUIDE.md exists."""
+ guide_path = REPO_ROOT / "LAUNCHD_SERVICE_GUIDE.md"
+ assert guide_path.exists(), "LAUNCHD_SERVICE_GUIDE.md not found"
+
+ # Check it has key sections
+ content = guide_path.read_text()
+ assert "Installation Steps" in content
+ assert "Loading and Managing the Service" in content
+ assert "Testing the Service" in content
+ assert "Troubleshooting" in content
+
+
+@pytest.mark.slow
+def test_orchestrator_module_can_run():
+ """Test that orchestrator can be run as a module (with quick timeout)."""
+ # This test tries to run the module but times out quickly
+ # Just verifying it starts without import errors
+ import signal
+
+ proc = subprocess.Popen(
+ [sys.executable, "-m", "privaseeai_security.orchestrator"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+
+ try:
+ # Let it run for 2 seconds
+ stdout, stderr = proc.communicate(timeout=2)
+ # If we get here, process exited early (might be an error)
+ assert False, f"Process exited unexpectedly: {stderr}"
+ except subprocess.TimeoutExpired:
+ # This is expected - the daemon should keep running
+ proc.kill()
+ stdout, stderr = proc.communicate()
+
+ # Check for import errors or other startup failures in output
+ combined = (stdout + stderr).lower()
+ assert "traceback" not in combined or "modulenotfounderror" not in combined, \
+ f"Module had errors: {stdout}\n{stderr}"
From 3892e47fdcc03962f393e3f21078e3f13cb8836f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:34:02 +0000
Subject: [PATCH 4/6] Fix code review issues: assertion logic, asyncio event
creation, and docstring placement
Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
---
LAUNCHD_QUICK_START.md | 75 +++++++++++++++++++++++++
src/privaseeai_security/daemon.py | 23 +++++---
src/privaseeai_security/orchestrator.py | 2 +-
tests/unit/test_launchd_service.py | 2 +-
4 files changed, 93 insertions(+), 9 deletions(-)
create mode 100644 LAUNCHD_QUICK_START.md
diff --git a/LAUNCHD_QUICK_START.md b/LAUNCHD_QUICK_START.md
new file mode 100644
index 0000000..6f1ec86
--- /dev/null
+++ b/LAUNCHD_QUICK_START.md
@@ -0,0 +1,75 @@
+# Quick Start: launchd Service
+
+This is a quick reference for installing and running PrivaseeAI Security as a macOS background service.
+
+## Files Created
+
+1. **com.privaseeai.security.plist** - launchd configuration file
+2. **LAUNCHD_SERVICE_GUIDE.md** - Complete installation and management guide
+3. **src/privaseeai_security/daemon.py** - Alternative daemon entry point
+4. **Orchestrator daemon mode** - Added `__main__` block to orchestrator.py
+
+## Quick Installation
+
+```bash
+# 1. Install the package
+pip install -e .
+
+# 2. Create required directories
+sudo mkdir -p /var/log/privaseeai /opt/privaseeai
+sudo chown $(whoami):staff /var/log/privaseeai /opt/privaseeai
+
+# 3. Install the service
+cp com.privaseeai.security.plist ~/Library/LaunchAgents/
+
+# 4. Load and start
+launchctl load ~/Library/LaunchAgents/com.privaseeai.security.plist
+
+# 5. Verify it's running
+launchctl list | grep com.privaseeai.security
+tail -f /var/log/privaseeai/security.log
+```
+
+## Service Features
+
+✅ **Auto-start on boot** - Runs automatically when you log in
+✅ **Auto-restart on crash** - Keeps running with 60-second throttle
+✅ **Centralized logging** - All output goes to `/var/log/privaseeai/security.log`
+✅ **User agent** - Runs as your user, not as root
+✅ **Python module execution** - Runs via `python -m privaseeai_security.orchestrator`
+
+## Quick Commands
+
+```bash
+# Status
+launchctl list | grep com.privaseeai.security
+
+# Logs
+tail -f /var/log/privaseeai/security.log
+
+# Restart
+launchctl stop com.privaseeai.security
+
+# Unload
+launchctl unload ~/Library/LaunchAgents/com.privaseeai.security.plist
+```
+
+## Test the Service
+
+```bash
+# Run the complete test suite
+python3 -m pytest tests/unit/test_launchd_service.py -v
+
+# Test running orchestrator manually
+python3 -m privaseeai_security.orchestrator
+# Press Ctrl+C to stop
+```
+
+## For Full Documentation
+
+See **LAUNCHD_SERVICE_GUIDE.md** for:
+- Detailed installation steps
+- Configuration options
+- Troubleshooting guide
+- Advanced customization
+- Security considerations
diff --git a/src/privaseeai_security/daemon.py b/src/privaseeai_security/daemon.py
index 7eb165f..8269f8e 100644
--- a/src/privaseeai_security/daemon.py
+++ b/src/privaseeai_security/daemon.py
@@ -22,19 +22,32 @@
# Global orchestrator instance for signal handling
_orchestrator: Optional[ThreatOrchestrator] = None
-_shutdown_event = asyncio.Event()
def signal_handler(signum, frame):
"""Handle shutdown signals gracefully."""
logger.info(f"Received signal {signum}, initiating shutdown...")
- _shutdown_event.set()
+ # Set the shutdown event if it exists in the current context
+ # The event is created in run_daemon() to avoid asyncio issues
async def run_daemon():
"""Run the threat orchestrator as a daemon service."""
global _orchestrator
+ # Create shutdown event inside async context
+ shutdown_event = asyncio.Event()
+
+ def _signal_handler(signum, frame):
+ """Handle shutdown signals."""
+ logger.info(f"Received signal {signum}, initiating shutdown...")
+ shutdown_event.set()
+
+ # Setup signal handlers
+ import signal
+ signal.signal(signal.SIGTERM, _signal_handler)
+ signal.signal(signal.SIGINT, _signal_handler)
+
logger.info("Starting PrivaseeAI Security daemon...")
# Create orchestrator instance
@@ -51,7 +64,7 @@ async def run_daemon():
logger.info("✅ Orchestrator started successfully")
# Wait for shutdown signal
- await _shutdown_event.wait()
+ await shutdown_event.wait()
except Exception as e:
logger.error(f"Daemon error: {e}", exc_info=True)
@@ -66,10 +79,6 @@ async def run_daemon():
def main():
"""Main entry point for daemon."""
- # Setup signal handlers
- signal.signal(signal.SIGTERM, signal_handler)
- signal.signal(signal.SIGINT, signal_handler)
-
# Log startup
logger.info("PrivaseeAI Security Daemon starting...")
diff --git a/src/privaseeai_security/orchestrator.py b/src/privaseeai_security/orchestrator.py
index 832d7d8..86d1e8c 100644
--- a/src/privaseeai_security/orchestrator.py
+++ b/src/privaseeai_security/orchestrator.py
@@ -415,8 +415,8 @@ def signal_handler(signum, frame):
logger.info("✅ Orchestrator daemon stopped")
+# Entry point for python -m privaseeai_security.orchestrator
if __name__ == "__main__":
- """Entry point for python -m privaseeai_security.orchestrator"""
import sys
logger.info("Starting PrivaseeAI Security Orchestrator daemon...")
diff --git a/tests/unit/test_launchd_service.py b/tests/unit/test_launchd_service.py
index 20158bb..0e8157b 100644
--- a/tests/unit/test_launchd_service.py
+++ b/tests/unit/test_launchd_service.py
@@ -133,5 +133,5 @@ def test_orchestrator_module_can_run():
# Check for import errors or other startup failures in output
combined = (stdout + stderr).lower()
- assert "traceback" not in combined or "modulenotfounderror" not in combined, \
+ assert "traceback" not in combined and "modulenotfounderror" not in combined, \
f"Module had errors: {stdout}\n{stderr}"
From d928d53084cc582cca2af24fdec33464ed97bf75 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:43:04 +0000
Subject: [PATCH 5/6] Fix coverage configuration to use relative file paths
Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index 8fae5c9..63039b3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,6 +51,7 @@ markers = [
[tool.coverage.run]
source = ["src"]
+relative_files = true
omit = [
"*/tests/*",
"*/__pycache__/*",
From 096f1a58fb93a2652217f60ec8a6721decae7088 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:52:57 +0000
Subject: [PATCH 6/6] Apply code review feedback: fix Python path, comments,
imports, and security
Co-authored-by: aurelianware <194855645+aurelianware@users.noreply.github.com>
---
LAUNCHD_QUICK_START.md | 3 ++-
LAUNCHD_SERVICE_GUIDE.md | 10 +++++-----
com.privaseeai.security.plist | 9 +++++----
src/privaseeai_security/daemon.py | 9 ---------
tests/unit/test_launchd_service.py | 3 +--
5 files changed, 13 insertions(+), 21 deletions(-)
diff --git a/LAUNCHD_QUICK_START.md b/LAUNCHD_QUICK_START.md
index 6f1ec86..0269f89 100644
--- a/LAUNCHD_QUICK_START.md
+++ b/LAUNCHD_QUICK_START.md
@@ -18,6 +18,7 @@ pip install -e .
# 2. Create required directories
sudo mkdir -p /var/log/privaseeai /opt/privaseeai
sudo chown $(whoami):staff /var/log/privaseeai /opt/privaseeai
+chmod 700 /var/log/privaseeai
# 3. Install the service
cp com.privaseeai.security.plist ~/Library/LaunchAgents/
@@ -33,7 +34,7 @@ tail -f /var/log/privaseeai/security.log
## Service Features
✅ **Auto-start on boot** - Runs automatically when you log in
-✅ **Auto-restart on crash** - Keeps running with 60-second throttle
+✅ **Auto-restart on crash** - Keeps running with 60-second minimum restart delay
✅ **Centralized logging** - All output goes to `/var/log/privaseeai/security.log`
✅ **User agent** - Runs as your user, not as root
✅ **Python module execution** - Runs via `python -m privaseeai_security.orchestrator`
diff --git a/LAUNCHD_SERVICE_GUIDE.md b/LAUNCHD_SERVICE_GUIDE.md
index 23ef300..b1a08e5 100644
--- a/LAUNCHD_SERVICE_GUIDE.md
+++ b/LAUNCHD_SERVICE_GUIDE.md
@@ -8,15 +8,15 @@ The `com.privaseeai.security.plist` file configures PrivaseeAI Security to run a
- ✅ Runs `python -m privaseeai_security.orchestrator` automatically
- ✅ Starts on boot (user agent)
-- ✅ Auto-restarts if crashed (with exponential backoff via ThrottleInterval)
+- ✅ Auto-restarts if crashed (with a minimum restart delay enforced by ThrottleInterval)
- ✅ Logs to `/var/log/privaseeai/security.log`
- ✅ Runs from `/opt/privaseeai` working directory
- ✅ Executes as the current user
-- ✅ Prevents rapid restart loops with 60-second throttle
+- ✅ Prevents rapid restart loops with a fixed 60-second ThrottleInterval between restarts
## Prerequisites
-1. **Python 3.11+** installed at `/usr/bin/python3`
+1. **Python 3.11+** - Ensure a Python 3.11+ interpreter is available in your PATH (via Homebrew, venv, or system Python)
2. **PrivaseeAI Security** installed as a Python package
3. **Proper permissions** to write to `/var/log/privaseeai/`
@@ -45,8 +45,8 @@ sudo mkdir -p /var/log/privaseeai
# Set ownership to current user
sudo chown $(whoami):staff /var/log/privaseeai
-# Set appropriate permissions
-chmod 755 /var/log/privaseeai
+# Restrict permissions so only this user can access logs
+chmod 700 /var/log/privaseeai
```
### 3. Set Up Working Directory
diff --git a/com.privaseeai.security.plist b/com.privaseeai.security.plist
index fc8d121..aea6044 100644
--- a/com.privaseeai.security.plist
+++ b/com.privaseeai.security.plist
@@ -9,7 +9,8 @@
ProgramArguments
- /usr/bin/python3
+ /usr/bin/env
+ python3
-m
privaseeai_security.orchestrator
@@ -25,13 +26,13 @@
KeepAlive
-
+
SuccessfulExit
-
-
+
+
ThrottleInterval
60
diff --git a/src/privaseeai_security/daemon.py b/src/privaseeai_security/daemon.py
index 8269f8e..a240bdf 100644
--- a/src/privaseeai_security/daemon.py
+++ b/src/privaseeai_security/daemon.py
@@ -10,7 +10,6 @@
import asyncio
import signal
import sys
-from pathlib import Path
from typing import Optional
from .orchestrator import ThreatOrchestrator
@@ -24,13 +23,6 @@
_orchestrator: Optional[ThreatOrchestrator] = None
-def signal_handler(signum, frame):
- """Handle shutdown signals gracefully."""
- logger.info(f"Received signal {signum}, initiating shutdown...")
- # Set the shutdown event if it exists in the current context
- # The event is created in run_daemon() to avoid asyncio issues
-
-
async def run_daemon():
"""Run the threat orchestrator as a daemon service."""
global _orchestrator
@@ -44,7 +36,6 @@ def _signal_handler(signum, frame):
shutdown_event.set()
# Setup signal handlers
- import signal
signal.signal(signal.SIGTERM, _signal_handler)
signal.signal(signal.SIGINT, _signal_handler)
diff --git a/tests/unit/test_launchd_service.py b/tests/unit/test_launchd_service.py
index 0e8157b..adfb6ee 100644
--- a/tests/unit/test_launchd_service.py
+++ b/tests/unit/test_launchd_service.py
@@ -112,7 +112,6 @@ def test_orchestrator_module_can_run():
"""Test that orchestrator can be run as a module (with quick timeout)."""
# This test tries to run the module but times out quickly
# Just verifying it starts without import errors
- import signal
proc = subprocess.Popen(
[sys.executable, "-m", "privaseeai_security.orchestrator"],
@@ -125,7 +124,7 @@ def test_orchestrator_module_can_run():
# Let it run for 2 seconds
stdout, stderr = proc.communicate(timeout=2)
# If we get here, process exited early (might be an error)
- assert False, f"Process exited unexpectedly: {stderr}"
+ pytest.fail(f"Process exited unexpectedly: {stderr}")
except subprocess.TimeoutExpired:
# This is expected - the daemon should keep running
proc.kill()