Initial commit
This commit is contained in:
commit
d9ecaa6df3
91
Install-Queue-Worker-Service.ps1
Normal file
91
Install-Queue-Worker-Service.ps1
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<#
|
||||||
|
Copyright (c) 2025 Dávid Lábodi
|
||||||
|
Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
$serviceName = "LaravelQueueWorker"
|
||||||
|
$workerScriptName = "queue-worker.ps1"
|
||||||
|
$serviceDisplayName = "Laravel Queue Worker"
|
||||||
|
$serviceDescription = "Runs Laravel queue worker process"
|
||||||
|
|
||||||
|
$scriptDir = $PSScriptRoot # Need to place this script too in the laravel project root directory!
|
||||||
|
$logDir = Join-Path $scriptDir -ChildPath "storage\logs"
|
||||||
|
$nssmDir = Join-Path $scriptDir "nssm\nssm-2.24-101-g897c7ad\win64"
|
||||||
|
$nssmExe = Join-Path $nssmDir "nssm.exe"
|
||||||
|
$workerScriptPath = Join-Path $scriptDir $workerScriptName
|
||||||
|
|
||||||
|
if (-not (Test-Path $LogDir)) {
|
||||||
|
Write-Host "Laravel log directory not found, ensure the script is in the laravel root directory." -ForegroundColor Red
|
||||||
|
Read-Host
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the script is running as an administrator
|
||||||
|
function Test-Admin {
|
||||||
|
$currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||||
|
return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart the script as an administrator if not already running as one
|
||||||
|
if (-not (Test-Admin)) {
|
||||||
|
Write-Host "This script requires administrator privileges." -ForegroundColor Yellow
|
||||||
|
Write-Host "Restarting script with elevated permissions..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Start a new PowerShell process with elevated privileges
|
||||||
|
$newProcess = Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs -PassThru
|
||||||
|
|
||||||
|
# Wait for the new process to exit
|
||||||
|
$newProcess.WaitForExit()
|
||||||
|
|
||||||
|
# Exit the current script
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download and extract NSSM if missing
|
||||||
|
if (-not (Test-Path $nssmExe)) {
|
||||||
|
Write-Host "Downloading NSSM..."
|
||||||
|
$nssmZipUrl = "https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip"
|
||||||
|
$zipPath = Join-Path $env:TEMP "nssm.zip"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $nssmZipUrl -OutFile $zipPath
|
||||||
|
Expand-Archive -Path $zipPath -DestinationPath $nssmDir
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path $zipPath) { Remove-Item $zipPath }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify worker script exists
|
||||||
|
if (-not (Test-Path $workerScriptPath)) {
|
||||||
|
throw "Worker script ($workerScriptName) not found in script directory!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check existing service
|
||||||
|
if (Get-Service $serviceName -ErrorAction SilentlyContinue) {
|
||||||
|
throw "Service $serviceName already exists! Uninstall it first."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get PowerShell path
|
||||||
|
$powerShellPath = (Get-Command "powershell.exe").Path
|
||||||
|
|
||||||
|
# Install service
|
||||||
|
& $nssmExe install $serviceName $powerShellPath "-ExecutionPolicy Bypass -File `"$workerScriptPath`""
|
||||||
|
& $nssmExe set $serviceName DisplayName $serviceDisplayName
|
||||||
|
& $nssmExe set $serviceName Description $serviceDescription
|
||||||
|
& $nssmExe set $serviceName AppDirectory $scriptDir
|
||||||
|
& $nssmExe set $serviceName Start SERVICE_AUTO_START
|
||||||
|
& $nssmExe set $serviceName AppStdout (Join-Path $logDir "queue-service.log")
|
||||||
|
& $nssmExe set $serviceName AppStderr (Join-Path $logDir "queue-service-error.log")
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "==========================================="
|
||||||
|
Start-Service $serviceName
|
||||||
|
Write-Host "Service installed and started successfully!" -ForeGroundColor Green
|
||||||
|
Write-Host "Service Name: $serviceName" -ForeGroundColor Green
|
||||||
|
Write-Host "Worker Script: $workerScriptPath" -ForeGroundColor Green
|
||||||
|
Write-Host "==========================================="
|
||||||
|
Write-Host "Don't forget to add NSSM directory ($($nssmDir)) to .gitignore if this is a development instance!"
|
||||||
|
Read-Host
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Dávid Lábodi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Laravel Queue Worker Windows Service
|
||||||
|
|
||||||
|
**PowerShell scripts** to install/uninstall a **Laravel Queue Worker Wrapper Script as a Windows service** using [NSSM (Non-Sucking Service Manager)](https://nssm.cc).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🛠️ Automated easy service installation/uninstallation
|
||||||
|
- 🔄 **Auto-restarting worker** with configurable delay
|
||||||
|
- 📅 **Time-stamped logging** integrated with Laravel's log system
|
||||||
|
- 📊 **Dual logging** (file + console if wrapper run directly) with ERROR tagging
|
||||||
|
- 🚦 **Process monitoring** with output/error stream handling
|
||||||
|
- 📁 Automated NSSM setup (downloaded on first run)
|
||||||
|
- 📝 Integrated logging for service operations (NSSM)
|
||||||
|
- 🔄 **Automatic service restart** on failure (NSSM)
|
||||||
|
- 🛠️ **Self-contained configuration** with automatic path detection
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
```
|
||||||
|
{LARAVEL_ROOT}/
|
||||||
|
├── Install-Queue-Worker-Service.ps1 # Service installation script
|
||||||
|
├── Uninstall-Queue-Worker-Service.ps1 # Service removal script
|
||||||
|
├── queue-worker.ps1 # Worker process wrapper, this is what the service run
|
||||||
|
└── /storage/logs/ # Laravel logs (auto-used)
|
||||||
|
└── queue-worker.txt # Real-time logging by worker wrapper
|
||||||
|
├── queue-worker.log # Real-time logging by worker wrapper
|
||||||
|
├── queue-service.log # Service output logs (auto-created, handled by NSSM)
|
||||||
|
└── queue-service-error.log # Service error logs (auto-created, handled by NSSM)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
1. **Clone repository:**
|
||||||
|
````powershell
|
||||||
|
git clone https://github.com/labodidavid/laravel-queue-service-win.git
|
||||||
|
````
|
||||||
|
2. **Place scripts** in your Laravel root directory
|
||||||
|
3. **Run as Administrator**:
|
||||||
|
```powershell
|
||||||
|
# Install service
|
||||||
|
.\Install-Queue-Worker-Service.ps1
|
||||||
|
```
|
||||||
|
4. **In development environments:** Add `/nssm` directory or the scripts to `.gitignore`
|
||||||
|
|
||||||
|
**Uninstall** when needed:
|
||||||
|
```powershell
|
||||||
|
.\Uninstall-Service.ps1
|
||||||
|
```
|
||||||
|
Alternatively you can run the worker wrapper without the service install:
|
||||||
|
```powershell
|
||||||
|
# Run worker directly (no service)
|
||||||
|
.\queue-worker.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Edit these in `queue-worker.ps1`:
|
||||||
|
```powershell
|
||||||
|
# Worker arguments (add --queue, --sleep, etc.)
|
||||||
|
$CommandArgs = "artisan queue:work --tries=3 --timeout=900"
|
||||||
|
|
||||||
|
# Restart delay (seconds)
|
||||||
|
$RestartDelay = 10
|
||||||
|
|
||||||
|
# Log location (default: storage/logs/queue-worker.txt)
|
||||||
|
$LogPath = "C:\custom\logs\worker.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
- *Permission errors*: Ensure write access to `storage/logs`
|
||||||
|
- *Missing PHP*: Verify PHP is in system PATH
|
||||||
|
- *Service not starting*: Check `Event Viewer → Windows Logs → Application or ` or `queue-service.log`
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- Windows Server 2012+ / Windows 10+
|
||||||
|
- PowerShell 5.1+
|
||||||
|
- An initialized Laravel project
|
||||||
|
- NSSM (auto-downloaded by install script)
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
|
||||||
|
2. Create a feature branch (`git checkout -b feature/improvement`)
|
||||||
|
|
||||||
|
3. Commit changes (`git commit -am 'Add some feature'`)
|
||||||
|
|
||||||
|
4. Push to branch (`git push origin feature/improvement`)
|
||||||
|
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
77
Uninstall-Queue-Worker-Service.ps1
Normal file
77
Uninstall-Queue-Worker-Service.ps1
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<#
|
||||||
|
Copyright (c) 2025 Dávid Lábodi
|
||||||
|
Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
$serviceName = "LaravelQueueWorker"
|
||||||
|
$scriptDir = $PSScriptRoot # Define $scriptDir as the directory where the script is located
|
||||||
|
$nssmDir = Join-Path $scriptDir "nssm\nssm-2.24-101-g897c7ad\win64"
|
||||||
|
$nssmExe = Join-Path $nssmDir "nssm.exe"
|
||||||
|
|
||||||
|
# Check if the script is running as an administrator
|
||||||
|
function Test-Admin {
|
||||||
|
$currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||||
|
return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart the script as an administrator if not already running as one
|
||||||
|
if (-not (Test-Admin)) {
|
||||||
|
Write-Host "This script requires administrator privileges." -ForegroundColor Yellow
|
||||||
|
Write-Host "Restarting script with elevated permissions..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Start a new PowerShell process with elevated privileges
|
||||||
|
$newProcess = Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs -PassThru
|
||||||
|
|
||||||
|
# Wait for the new process to exit
|
||||||
|
$newProcess.WaitForExit()
|
||||||
|
|
||||||
|
# Exit the current script
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the script reaches this point, it is running as an administrator
|
||||||
|
Write-Host "Running with administrator privileges." -ForegroundColor Green
|
||||||
|
|
||||||
|
# Check if NSSM exists
|
||||||
|
if (-not (Test-Path $nssmExe)) {
|
||||||
|
Write-Host "NSSM executable not found at $nssmExe. Please ensure NSSM is installed." -ForegroundColor Red
|
||||||
|
Read-Host "Press Enter to exit"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if service exists
|
||||||
|
$service = Get-Service $serviceName -ErrorAction SilentlyContinue
|
||||||
|
if (-not $service) {
|
||||||
|
Write-Host "Service $serviceName does not exist." -ForegroundColor Red
|
||||||
|
Read-Host "Press Enter to exit"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop and remove service
|
||||||
|
try {
|
||||||
|
if ($service.Status -eq "Running") {
|
||||||
|
Write-Host "Stopping service $serviceName..." -ForegroundColor Yellow
|
||||||
|
Stop-Service $serviceName -Force
|
||||||
|
Write-Host "Service stopped." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Removing service $serviceName..." -ForegroundColor Yellow
|
||||||
|
& $nssmExe remove $serviceName confirm
|
||||||
|
Write-Host "Service uninstalled." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "An error occurred: $_" -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Ensure the script is running as admin privileges, and the service name is correct!" -ForegroundColor Red
|
||||||
|
Read-Host "Press Enter to exit"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional: Cleanup NSSM (comment out if you want to remove)
|
||||||
|
# Remove-Item $nssmDir -Recurse -Force
|
||||||
|
# Write-Host "NSSM uninstalled." -ForeGroundColor Green
|
||||||
|
|
||||||
|
Read-Host "Press Enter to exit"
|
115
queue-worker.ps1
Normal file
115
queue-worker.ps1
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<#
|
||||||
|
Copyright (c) 2025 Dávid Lábodi
|
||||||
|
Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$startingHourMinute = Get-Date -Format "HHmm" # Script start hour and minute for include in the log file name
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
$LaravelPath = $PSScriptRoot # Path to your Laravel project
|
||||||
|
$PhpPath = (Get-Command php.exe).Source # Path to your PHP executable
|
||||||
|
$LogDir = "$LaravelPath\storage\logs" # Path to the log dir
|
||||||
|
$LogPath = "$($LogDir)\queue-worker.txt" # Path to the default log file
|
||||||
|
$CommandArgs = "artisan queue:work --tries=3" # Artisan command arguments
|
||||||
|
$RestartDelay = 5 # Delay in seconds before restarting
|
||||||
|
|
||||||
|
|
||||||
|
if (-not (Test-Path $LogDir)) {
|
||||||
|
Write-Host "Laravel log directory not found, ensure the script is in the laravel root directory." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function Log {
|
||||||
|
param (
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
$FinalText = "[$($Timestamp)] $($Message)"
|
||||||
|
|
||||||
|
# Write to the log file
|
||||||
|
$FinalText | Out-File -FilePath $LogPath -Append -Encoding utf8
|
||||||
|
|
||||||
|
# Optionally, display in the console
|
||||||
|
Write-Host $FinalText
|
||||||
|
}
|
||||||
|
|
||||||
|
Log -Message "Starting, monitoring Laravel Queue Worker..."
|
||||||
|
Write-Host "Log directory: $LogDir"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Variable to store the current worker process
|
||||||
|
$WorkerProcess = $null
|
||||||
|
# Cleanup function to terminate the worker process
|
||||||
|
function Cleanup-Worker {
|
||||||
|
if ($WorkerProcess -and !$WorkerProcess.HasExited) {
|
||||||
|
Log -Message "Stopping artisan queue worker..."
|
||||||
|
$WorkerProcess.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register cleanup on PowerShell session exit
|
||||||
|
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { Cleanup-Worker } | Out-Null
|
||||||
|
|
||||||
|
$StartInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
|
||||||
|
FileName = $PhpPath
|
||||||
|
Arguments = $CommandArgs
|
||||||
|
UseShellExecute = $false
|
||||||
|
RedirectStandardOutput = $true
|
||||||
|
RedirectStandardError = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to read and log process output
|
||||||
|
function Read-ProcessOutput {
|
||||||
|
param (
|
||||||
|
[System.Diagnostics.Process]$Process
|
||||||
|
)
|
||||||
|
|
||||||
|
while (!$Process.HasExited) {
|
||||||
|
while (!$Process.StandardOutput.EndOfStream) {
|
||||||
|
$Output = $Process.StandardOutput.ReadLine()
|
||||||
|
Log $Output
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!$Process.StandardError.EndOfStream) {
|
||||||
|
$Err = $Process.StandardError.ReadLine()
|
||||||
|
Log "ERROR: $Err"
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Milliseconds 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read remaining output after the process exits
|
||||||
|
while (!$Process.StandardOutput.EndOfStream) {
|
||||||
|
$Output = $Process.StandardOutput.ReadLine()
|
||||||
|
Log $Output
|
||||||
|
}
|
||||||
|
while (!$Process.StandardError.EndOfStream) {
|
||||||
|
$Err = $Process.StandardError.ReadLine()
|
||||||
|
Log "ERROR: $Err"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Monitor loop
|
||||||
|
while ($true) {
|
||||||
|
# Create new process
|
||||||
|
$WorkerProcess = New-Object System.Diagnostics.Process
|
||||||
|
# Assign previously created StartInfo properties
|
||||||
|
$WorkerProcess.StartInfo = $StartInfo
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Start process
|
||||||
|
[void]$WorkerProcess.Start()
|
||||||
|
Write-Host "Process started."
|
||||||
|
|
||||||
|
# Read and log process output
|
||||||
|
Read-ProcessOutput -Process $WorkerProcess
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Host "Error: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log the restart event
|
||||||
|
Log "Artisan queue worker stopped. Restarting in $RestartDelay seconds..."
|
||||||
|
# Delay before restarting
|
||||||
|
Start-Sleep -Seconds $RestartDelay
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user