Initial commit

This commit is contained in:
LabodiDavid 2025-02-04 13:27:46 +01:00
commit d9ecaa6df3
5 changed files with 398 additions and 0 deletions

View 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
View 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
View 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.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

View 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
View 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
}