Skip to content

Commit

Permalink
Fix the macos part
Browse files Browse the repository at this point in the history
  • Loading branch information
mdaneri committed Nov 23, 2024
1 parent ad7a7e8 commit d18b1f6
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 39 deletions.
3 changes: 3 additions & 0 deletions docs/Hosting/RunAsService.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ The `Register-PodeService` function offers several parameters to customize your
- **`-LinuxUser`** *(string)*:
Specifies the username under which the service will run. Defaults to the current user (Linux Only).

- **`-Agent`** *(switch)*:
Create an Agent instead of a Daemon in MacOS (MacOS Only).

- **`-Start`** *(switch)*:
Starts the service immediately after registration.

Expand Down
2 changes: 1 addition & 1 deletion examples/HelloService/HelloService.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ catch {


if ( $Register.IsPresent) {
Register-PodeService -Name 'Hello Service' -ParameterString "-Port $Port" -Password $Password
Register-PodeService -Name 'Hello Service' -ParameterString "-Port $Port" -Password $Password -Agent
exit
}
if ( $Unregister.IsPresent) {
Expand Down
28 changes: 21 additions & 7 deletions src/PodeMonitor/PodeMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,33 @@ public PodeMonitor(PodeMonitorWorkerOptions options)
// Define the state file path only for Linux/macOS
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
string stateDirectory = OperatingSystem.IsLinux() ? "/run/podemonitor" :
OperatingSystem.IsMacOS() ? "/private/var/run/podemonitor" :
throw new PlatformNotSupportedException("The current platform is not supported for setting the state directory.");

if (!Directory.Exists(stateDirectory))
string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string stateDirectory = OperatingSystem.IsLinux()
? "/run/podemonitor"
: OperatingSystem.IsMacOS()
? Path.Combine(homeDirectory, "Library", "LaunchAgents", "PodeMonitor")
: throw new PlatformNotSupportedException("The current platform is not supported for setting the state directory.");
try
{
Directory.CreateDirectory(stateDirectory);
if (!Directory.Exists(stateDirectory))
{
Directory.CreateDirectory(stateDirectory);
}
}
catch (Exception ex)
{
PodeMonitorLogger.Log(LogLevel.ERROR, "PodeMonitor", Environment.ProcessId,
$"Failed to create state directory at {stateDirectory}: {ex.Message}");
throw;
}

// Define the state file path (default to /var/tmp for Linux/macOS)
_stateFilePath = Path.Combine(stateDirectory, $"{Environment.ProcessId}.state");


PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Initialized PodeMonitor with pipe name: {_pipeName} and state file: {_stateFilePath}");
}

}

/// <summary>
Expand Down Expand Up @@ -180,8 +192,10 @@ public void StopPowerShellProcess()
PodeMonitorLogger.Log(LogLevel.INFO, "PodeMonitor", Environment.ProcessId, $"Waiting for {_shutdownWaitTimeMs} milliseconds for Pode process to exit...");
WaitForProcessExit(_shutdownWaitTimeMs);

if (_powerShellProcess != null || !_powerShellProcess.HasExited || Process.GetProcessById(_powerShellProcess.Id) != null)
if (_powerShellProcess != null && !_powerShellProcess.HasExited )
{
PodeMonitorLogger.Log(LogLevel.WARN, "PodeMonitor", Environment.ProcessId, $"Pode process has exited:{_powerShellProcess.HasExited} Id:{_powerShellProcess.Id}");

PodeMonitorLogger.Log(LogLevel.WARN, "PodeMonitor", Environment.ProcessId, "Pode process did not terminate gracefully. Killing process.");
_powerShellProcess.Kill();
}
Expand Down
46 changes: 37 additions & 9 deletions src/Private/Service.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function Start-PodeServiceHearthbeat {

# Define the script block for the client receiver, listens for commands via the named pipe
$scriptBlock = {
$serviceState='running'
$serviceState = 'running'
while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {

Write-PodeHost -Message "Initialize Listener Pipe $($PodeContext.Server.Service.PipeName)" -Force
Expand Down Expand Up @@ -201,6 +201,9 @@ function Start-PodeServiceHearthbeat {
.PARAMETER OsArchitecture
Specifies the architecture of the operating system (e.g., `osx-x64` or `osx-arm64`).
.PARAMETER Agent
A switch to create an Agent instead of a Daemon in MacOS.
.OUTPUTS
Returns $true if successful.
Expand Down Expand Up @@ -235,7 +238,10 @@ function Register-PodeMacService {
$OsArchitecture,

[string]
$LogPath
$LogPath,

[switch]
$Agent
)

$nameService = "pode.$Name.service".Replace(' ', '_')
Expand All @@ -248,7 +254,12 @@ function Register-PodeMacService {

# Determine whether the service should run at load
$runAtLoad = if ($Autostart.IsPresent) { '<true/>' } else { '<false/>' }

if ($Agent) {
$plistPath = "$($HOME)/Library/LaunchAgents/$($nameService).plist"
}
else {
$plistPath = "/Library/LaunchDaemons/$($nameService).plist"
}
# Create the plist content
@"
<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -288,16 +299,20 @@ function Register-PodeMacService {
-->
</dict>
</plist>
"@ | Set-Content -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -Encoding UTF8
"@ | Set-Content -Path $plistPath -Encoding UTF8

Write-Verbose -Message "Service '$nameService' WorkingDirectory : $($BinPath)."

chmod +r "$($HOME)/Library/LaunchAgents/$($nameService).plist"
chmod +r $plistPath

try {
# Load the plist with launchctl
launchctl load "$($HOME)/Library/LaunchAgents/$($nameService).plist"

if ($Agent) {
launchctl load $plistPath
}
else {
sudo launchctl load $plistPat
}
# Verify the service is now registered
if (! (Test-PodeMacOsServiceIsRegistered $nameService)) {
# Service registration failed.
Expand Down Expand Up @@ -1011,7 +1026,13 @@ function Disable-PodeMacOsService {
[string]
$Name
)
$systemctlDisable = launchctl unload "$HOME/Library/LaunchAgents/$Name.plist" 2>&1
$sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($Name).plist" -PathType Leaf)
if ($sudo) {
$systemctlDisable = sudo launchctl unload "/Library/LaunchDaemons/$Name.plist" 2>&1
}
else {
$systemctlDisable = launchctl unload "$HOME/Library/LaunchAgents/$Name.plist" 2>&1
}
$success = $LASTEXITCODE -eq 0
Write-Verbose -Message ($systemctlDisable -join '`n')
return $success
Expand Down Expand Up @@ -1073,7 +1094,13 @@ function Start-PodeMacOsService {
[string]
$Name
)
$serviceStartInfo = launchctl start $Name 2>&1
$sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf)
if ($sudo) {
$serviceStartInfo = sudo launchctl start $Name 2>&1
}
else {
$serviceStartInfo = launchctl start $Name 2>&1
}
$success = $LASTEXITCODE -eq 0
Write-Verbose -Message ($serviceStartInfo -join "`n")
return $success
Expand Down Expand Up @@ -1101,6 +1128,7 @@ function Send-PodeServiceSignal {
'SIGHUP' = 1
'SIGTERM' = 15
}

$level = $signalMap[$Signal]
# Check if the service is registered
if ((Test-PodeServiceIsRegistered -Name $nameService)) {
Expand Down
29 changes: 24 additions & 5 deletions src/Public/Service.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
.PARAMETER LinuxUser
Specifies the username under which the service will run by default is the current user (Linux Only).
.PARAMETER Agent
A switch to create an Agent instead of a Daemon in MacOS (MacOS Only).
.PARAMETER Start
A switch to start the service immediately after registration.
Expand Down Expand Up @@ -109,6 +112,9 @@ function Register-PodeService {
[switch]
$Start,

[switch]
$Agent,

[securestring]
$Password,

Expand Down Expand Up @@ -240,6 +246,7 @@ function Register-PodeService {
User = $User
OsArchitecture = "osx-$osArchitecture"
LogPath = $LogPath
Agent = $Agent
}

$operation = Register-PodeMacService @param
Expand Down Expand Up @@ -846,7 +853,13 @@ function Unregister-PodeService {
}

if ((Disable-PodeMacOsService -Name $nameService)) {
$plistFilePath = "$HOME/Library/LaunchAgents/$nameService.plist"
$sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf)
if ($sudo) {
$plistFilePath = "/Library/LaunchDaemons/$nameService.plist"
}
else {
$plistFilePath = "$HOME/Library/LaunchAgents/$nameService.plist"
}
#Check if the plist file exists
if (Test-Path -Path $plistFilePath) {
# Read the content of the plist file
Expand Down Expand Up @@ -1031,12 +1044,18 @@ function Get-PodeService {
$servicePid = Get-PodeMacOsServicePid -Name $nameService # Extract the PID from the match

$sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf)
$stateFilePath = "/private/var/run/podemonitor/$servicePid.state"
if (Test-Path -Path $stateFilePath) {
$status = Get-Content -Path $stateFilePath
}

# Check if the service has a PID entry
if ($servicePid -ne 0) {
if ($sudo) {
$stateFilePath = "/Library/LaunchDaemons/PodeMonitor/$servicePid.state"
}
else {
$stateFilePath = "$($HOME)/Library/LaunchAgents/PodeMonitor/$servicePid.state"
}
if (Test-Path -Path $stateFilePath) {
$status = Get-Content -Path $stateFilePath
}
return @{
Name = $Name
Status = $status
Expand Down
53 changes: 43 additions & 10 deletions tests/integration/Service.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@ Describe 'Service Lifecycle' {

it 'register' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Register
Start-Sleep 8
$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Stopped'
if ($IsMacOS) {
$status.Status | Should -Be 'Running'
$status.Pid | Should -BeGreaterThan 0
}
else {
$status.Status | Should -Be 'Stopped'
$status.Pid | Should -Be 0
}

$status.Name | Should -Be 'Hello Service'
$status.Pid | Should -Be 0

}


it 'start' {
it 'start' -Skip:(!$IsMacOS) {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start
Start-Sleep 10
Start-Sleep 8
$webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue
$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Running'
Expand All @@ -28,18 +37,18 @@ Describe 'Service Lifecycle' {

it 'pause' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Suspend
Start-Sleep 10
# $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue
Start-Sleep 8
# $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue
$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Suspended'
$status.Name | Should -Be 'Hello Service'
$status.Pid | Should -BeGreaterThan 0
# $webRequest | Should -BeNullOrEmpty
# $webRequest | Should -BeNullOrEmpty
}

it 'resume' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -resume
Start-Sleep 10
Start-Sleep 8
$webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue
$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Running'
Expand All @@ -49,18 +58,42 @@ Describe 'Service Lifecycle' {
}
it 'stop' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop
Start-Sleep 10
Start-Sleep 8


$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Stopped'
$status.Name | Should -Be 'Hello Service'
$status.Pid | Should -Be 0

{Invoke-WebRequest -uri http://localhost:8080 }| Should -Throw
{ Invoke-WebRequest -uri http://localhost:8080 } | Should -Throw
}

it 're-start' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start
Start-Sleep 8
$webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue
$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Running'
$status.Name | Should -Be 'Hello Service'
$status.Pid | Should -BeGreaterThan 0
$webRequest.Content | Should -Be 'Hello, Service!'
}


it 're-stop' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop
Start-Sleep 8


$status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query
$status.Status | Should -Be 'Stopped'
$status.Name | Should -Be 'Hello Service'
$status.Pid | Should -Be 0

{ Invoke-WebRequest -uri http://localhost:8080 } | Should -Throw
}

it 'unregister' {
. "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Unregister
Start-Sleep 2
Expand Down
22 changes: 15 additions & 7 deletions tests/unit/Service.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ param()
BeforeAll {
$path = $PSCommandPath
$src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/'
Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ }
#Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode'
Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ }
}


Expand Down Expand Up @@ -195,7 +194,7 @@ Describe 'Start-PodeService' {
Assert-MockCalled -CommandName Start-PodeLinuxService -Exactly 0
}

It 'Throws an error if the service is not registered' -Skip:(!$IsLinux) {
It 'Return false if the service is not registered' -Skip:(!$IsLinux) {
Mock -CommandName Test-PodeLinuxServiceIsRegistered -MockWith { $false }
Start-PodeService -Name 'NonExistentService' | Should -BeFalse
}
Expand All @@ -204,9 +203,18 @@ Describe 'Start-PodeService' {
Context 'On macOS platform' {
It 'Starts a stopped service successfully' -Skip:(!$IsMacOS) {
Mock -CommandName Test-PodeMacOsServiceIsRegistered -MockWith { $true }
Mock -CommandName Test-PodeMacOsServiceIsActive -MockWith { $false }
Mock -CommandName Start-PodeMacOsService -MockWith { $true }
Mock -CommandName Test-PodeMacOsServiceIsActive -MockWith { $true }

$script:status = $null
Mock -CommandName Test-PodeMacOsServiceIsActive -MockWith {
if ($null -eq $script:status ) {
$script:status = $false
}
else {
$script:status = $true
}
return $script:status
}

# Act
Start-PodeService -Name 'MacService' | Should -Be $true
Expand All @@ -215,10 +223,10 @@ Describe 'Start-PodeService' {
Assert-MockCalled -CommandName Start-PodeMacOsService -Exactly 1
}

It 'Throws an error if the service is not registered' -Skip:(!$IsMacOS) {
It 'Return false if the service is not registered' -Skip:(!$IsMacOS) {
Mock -CommandName Test-PodeMacOsServiceIsRegistered -MockWith { $false }

{ Start-PodeService -Name 'NonExistentService' } | Should -Throw
Start-PodeService -Name 'NonExistentService' | Should -BeFalse
}
}
}
Expand Down

0 comments on commit d18b1f6

Please sign in to comment.