Skip to content

Instantly share code, notes, and snippets.

@maxfierke
Last active September 8, 2020 14:34
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save maxfierke/a85ba9d717d6e121405c to your computer and use it in GitHub Desktop.
Save maxfierke/a85ba9d717d6e121405c to your computer and use it in GitHub Desktop.
PM2 as a Windows Service under Local Service

PM2 as a Windows Service under Local Service

This is a PoC for running PM2 as a Windows Service under the Local Service account instead of the Local System account.

Prerequsites

  • Neither pm2 or pm2-windows-service installed yet. (The Powershell script will run npm i)
    • At the very least, you should run pm2-service-uninstall before running this script
  • npm and npm-cache global folders should be somewhere accessible to NT AUTHORITY\LocalService.

Use

  • Copy install.ps1 to the root of your node app

  • From an Elevated Powershell prompt run: .\install.ps1 -Pm2Home "C:\etc\.pm2" -AppStart "[path to node app start]"

  • Your Node app should be running under PM2. pm2 monit should show processes running.

# Deploys application as an app within a PM2 service
# Run from root of Node application
param(
[string] $Pm2Home = $env:PM2_HOME,
[string] $AppStart = "app.js"
)
$ErrorActionPreference = "Stop"
function Install-Node-Modules
{
Write-Host "Running npm install"
& "npm" i
}
function Create-Pm2-Home
{
Write-Host "Attempting to create $Pm2Home and give FullControl to LOCAL SERVICE"
New-Item -ItemType Directory -Force -Path $Pm2Home
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"LOCAL SERVICE", "FullControl", "ContainerInherit, ObjectInherit",
"None", "Allow")
try {
$acl = Get-Acl -Path $Pm2Home -ErrorAction Stop
$acl.SetAccessRule($rule)
Set-Acl -Path $Pm2Home -AclObject $acl -ErrorAction Stop
Write-Host "Successfully set FullControl permissions on $Pm2Home"
} catch {
throw "$Pm2Home : Failed to set permissions. Details : $_"
}
}
function Install-Pm2-Service
{
Write-Host "Installing pm2"
& "npm" i "pm2@latest" "-g"
Write-Host "Installing pm2-windows-service npm module"
& "npm" i pm2-windows-service "-g"
& "pm2-service-install"
# Create wrapper log file, otherwise it won't start
$wrapperLogPath = "$(npm config get prefix)\node_modules\pm2-windows-service\src\daemon\pm2.wrapper.log"
if (Test-Path $wrapperLogPath) {
Write-Debug "PM2 service wrapper log file already exists"
} else {
Out-File $wrapperLogPath -Encoding utf8
}
}
function Create-Pm2-Service-Config
{
param([string] $ConfigPath, [string] $CmdPath)
$configContent = @"
{
"apps": [{
"name": "node-app",
"script": "$($CmdPath -replace "\\","\\")",
"args": [],
"cwd": "$((Split-Path $CmdPath) -replace "\\","\\")",
"merge_logs": true,
"instances": 4,
"exec_mode": "cluster_mode",
"env": {
"NODE_ENV": "development"
}
}]
}
"@
# Write out config to JSON file
Write-Host "Writing PM2 service configuration to $ConfigPath"
$configContent | Out-File $ConfigPath -Encoding utf8
}
# From http://stackoverflow.com/a/4370900/964356
function Set-ServiceAcctCreds
{
param([string] $serviceName, [string] $newAcct, [string] $newPass)
$filter = "Name='$serviceName'"
$tries = 0
while (($service -eq $null -and $tries -le 3)) {
if ($tries -ne 0) {
sleep 2
}
$service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter
$tries = $tries + 1
}
if ($service -eq $null) {
throw "Could not find '$serviceName' service"
}
$service.Change($null,$null,$null,$null,$null,$null,$newAcct,$newPass)
$service.StopService()
while ($service.Started) {
sleep 2
$service = Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter $filter
}
$service.StartService()
}
function Change-Pm2-Service-Account
{
Write-Host "Changing PM2 to run as LOCAL SERVICE"
Set-ServiceAcctCreds -serviceName "pm2.exe" -newAcct "NT AUTHORITY\LocalService" -newPass ""
}
$env:PM2_HOME = $Pm2Home
$env:PM2_SERVICE_SCRIPTS = "$Pm2Home\ecosystem.json"
[Environment]::SetEnvironmentVariable("PM2_HOME", $env:PM2_HOME, "Machine")
[Environment]::SetEnvironmentVariable("PM2_SERVICE_SCRIPTS", $env:PM2_SERVICE_SCRIPTS, "Machine")
& Install-Node-Modules
& Create-Pm2-Home
& Create-Pm2-Service-Config -ConfigPath $env:PM2_SERVICE_SCRIPTS -CmdPath $AppStart
& Install-Pm2-Service
& Change-Pm2-Service-Account
@zubair1024
Copy link

Here is an alternate way of going about it:
https://gist.github.com/zubair1024/8f6126db7ffbafd706f0e328ef8d4662

@jessety
Copy link

jessety commented May 9, 2020

This is fantastic, thank you @maxfierke!

I adapted @mauron85's version of this script into a standalone installer, with a few additions:

  • Optionally automate migrating npm and npm-cache folders to C:\ProgramData\
  • Enable offline installation by caching packages on a build machine
  • Query for the Local Service user by its security identifier to better support non-English Windows installations

Hopefully this is helpful to someone: https://github.com/jessety/pm2-installer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment