MinusNow Documentation

📑 Table of Contents

🔍 Overview

This guide provides step-by-step instructions for deploying MinusNow ITSM on a Windows Server on-premises environment. The application uses Node.js runtime with PostgreSQL database and IIS as reverse proxy.

Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐ │ Load Balancer / DNS │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ IIS with URL Rewrite & ARR │ │ Port 80/443 (SSL) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ MinusNow ITSM Application │ │ Node.js Windows Service (Port 5000) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ PostgreSQL Database │ │ Port 5432 │ └─────────────────────────────────────────────────────────────────┘

💻 Server Requirements

Hardware Requirements

ComponentMinimumRecommendedEnterprise
CPU8 vCPU (x86_64)16 vCPU (Xeon/EPYC)32+ vCPU (Xeon/EPYC)
RAM32 GB DDR464 GB DDR4128+ GB DDR4/DDR5
Storage500 GB NVMe SSD1 TB NVMe SSD2+ TB NVMe SSD RAID
Network1 Gbps10 Gbps25 Gbps+

Software Requirements

ComponentVersionPurpose
OSWindows Server 2022 (Minimum 2019)Operating System
Node.js22.x LTS (Minimum 20.x LTS)Application Runtime
PostgreSQL16+ (Minimum 15)Database Server
IIS10.0+ (Windows Server 2022)Web Server / Reverse Proxy
URL Rewrite2.1+IIS Module
ARR3.0+Application Request Routing
NSSM2.24+ LatestService Wrapper
.NET Runtime8.0+IIS Dependencies

Network Requirements

PortProtocolDirectionPurpose
3389TCPInboundRDP Access
80TCPInboundHTTP (Redirect to HTTPS)
443TCPInboundHTTPS
5000TCPInternalApplication
5432TCPInternalPostgreSQL
25/587TCPOutboundSMTP Email

📋 Pre-Installation Checklist

Before proceeding, ensure you have:

  • Administrator access to the server
  • Static IP address assigned
  • DNS A record pointing to server IP (e.g., itsm.yourdomain.com)
  • SMTP server credentials for email notifications
  • SSL certificate (.pfx format or use Let's Encrypt)
  • Windows Firewall rules configured
  • Backup storage location identified

🔧 Server Preparation

1Install Windows Features

Open PowerShell as Administrator:

# Install IIS and required features Install-WindowsFeature -Name Web-Server -IncludeManagementTools Install-WindowsFeature -Name Web-WebSockets Install-WindowsFeature -Name Web-Asp-Net45 Install-WindowsFeature -Name Web-Http-Redirect # Verify installation Get-WindowsFeature | Where-Object {$_.Installed -eq $true} | Format-Table Name,Installed

2Configure Windows Firewall

# Allow HTTP New-NetFirewallRule -DisplayName "HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow # Allow HTTPS New-NetFirewallRule -DisplayName "HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow # Allow PostgreSQL (internal only) New-NetFirewallRule -DisplayName "PostgreSQL" -Direction Inbound -Protocol TCP -LocalPort 5432 -Action Allow -RemoteAddress LocalSubnet # Verify rules Get-NetFirewallRule | Where-Object {$_.Enabled -eq $true -and $_.Direction -eq 'Inbound'} | Format-Table DisplayName,Profile,Action

3Create Directory Structure

# Create application directories New-Item -ItemType Directory -Path "C:\MinusNow\ITSM" -Force New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data" -Force New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data\support-tickets" -Force New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data\ticket-responses" -Force New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\logs" -Force New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\backups" -Force New-Item -ItemType Directory -Path "C:\MinusNow\Tools" -Force # Set permissions $acl = Get-Acl "C:\MinusNow" $rule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($rule) Set-Acl "C:\MinusNow" $acl

4Install IIS URL Rewrite & ARR

# Download and install URL Rewrite $rewriteUrl = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi" Invoke-WebRequest -Uri $rewriteUrl -OutFile "C:\MinusNow\Tools\rewrite.msi" Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\rewrite.msi /quiet" -Wait # Download and install ARR $arrUrl = "https://download.microsoft.com/download/E/9/8/E9849D6A-020E-47E4-9FD0-A023E99B54EB/requestRouter_amd64.msi" Invoke-WebRequest -Uri $arrUrl -OutFile "C:\MinusNow\Tools\arr.msi" Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\arr.msi /quiet" -Wait # Enable ARR proxy Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled" -Value "True"

🗄️ Database Installation

1Download and Install PostgreSQL

# Download PostgreSQL installer $pgUrl = "https://get.enterprisedb.com/postgresql/postgresql-15.5-1-windows-x64.exe" Invoke-WebRequest -Uri $pgUrl -OutFile "C:\MinusNow\Tools\postgresql-installer.exe" # Run installer (interactive GUI) Start-Process "C:\MinusNow\Tools\postgresql-installer.exe" -Wait # Or silent install Start-Process "C:\MinusNow\Tools\postgresql-installer.exe" -ArgumentList @( "--mode unattended", "--unattendedmodeui minimal", "--superpassword YourSuperSecurePassword", "--serverport 5432" ) -Wait

2Configure PostgreSQL

# Add PostgreSQL to PATH (if not already) $env:Path += ";C:\Program Files\PostgreSQL\15\bin" [Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine) # Create database user and database $psqlCommands = @" CREATE USER minusnow WITH PASSWORD 'your_secure_password_here'; CREATE DATABASE minusnow_itsm OWNER minusnow; GRANT ALL PRIVILEGES ON DATABASE minusnow_itsm TO minusnow; \c minusnow_itsm CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; "@ $psqlCommands | & "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U postgres

3Configure pg_hba.conf

# Edit C:\Program Files\PostgreSQL\15\data\pg_hba.conf # Add these lines: # TYPE DATABASE USER ADDRESS METHOD # host minusnow_itsm minusnow 127.0.0.1/32 md5 # host minusnow_itsm minusnow ::1/128 md5 # Restart PostgreSQL service Restart-Service postgresql-x64-15

📦 Application Installation

1Install Node.js

# Download Node.js 20 LTS $nodeUrl = "https://nodejs.org/dist/v20.11.0/node-v20.11.0-x64.msi" Invoke-WebRequest -Uri $nodeUrl -OutFile "C:\MinusNow\Tools\node.msi" Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\node.msi /quiet" -Wait # Verify installation (open new PowerShell) node --version npm --version

2Download Application

# Option 1: Download from portal # Navigate to https://www.minusnow.com/site/downloads # Download minusnow-itsm-artifact.zip # Extract to application folder Expand-Archive -Path "C:\Users\Administrator\Downloads\minusnow-itsm-artifact.zip" -DestinationPath "C:\MinusNow\ITSM" -Force # Option 2: Use installation script Invoke-WebRequest -Uri "https://www.minusnow.com/site/download/install-windows.ps1" -OutFile "C:\MinusNow\Tools\install.ps1" & "C:\MinusNow\Tools\install.ps1"

3Configure Environment Variables

# Create .env file $envContent = @" # Application NODE_ENV=production PORT=5000 HOST=0.0.0.0 # Database DATABASE_URL=postgresql://minusnow:your_password@localhost:5432/minusnow_itsm # Session SESSION_SECRET=$([guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N')) # Email (SMTP) SMTP_HOST=smtp.yourdomain.com SMTP_PORT=587 SMTP_USER=noreply@yourdomain.com SMTP_PASS=your_smtp_password # Application URL APP_URL=https://itsm.yourdomain.com "@ $envContent | Out-File -FilePath "C:\MinusNow\ITSM\.env" -Encoding UTF8

4Install Dependencies and Build

cd C:\MinusNow\ITSM # Install dependencies npm install # Run database migrations npm run db:push # Build application (if from source) npm run build

🌐 IIS Configuration

1Create web.config

<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <!-- Redirect HTTP to HTTPS --> <rule name="HTTP to HTTPS" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="^OFF$" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule> <!-- Reverse proxy to Node.js with X-Forwarded headers --> <rule name="ReverseProxy" stopProcessing="true"> <match url="(.*)" /> <serverVariables> <set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" /> <set name="HTTP_X_FORWARDED_PROTO" value="{HTTPS}" /> <set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" /> <set name="HTTP_X_REAL_IP" value="{REMOTE_ADDR}" /> </serverVariables> <action type="Rewrite" url="http://localhost:5000/{R:1}" /> </rule> </rules> <!-- Allow server variables to be set --> <allowedServerVariables> <add name="HTTP_X_FORWARDED_FOR" /> <add name="HTTP_X_FORWARDED_PROTO" /> <add name="HTTP_X_FORWARDED_HOST" /> <add name="HTTP_X_REAL_IP" /> </allowedServerVariables> </rewrite> <!-- WebSocket support --> <webSocket enabled="true" /> <!-- Proxy settings --> <proxy enabled="true" preserveHostHeader="true" reverseRewriteHostInResponseHeaders="true" /> <!-- Security headers --> <httpProtocol> <customHeaders> <add name="X-Content-Type-Options" value="nosniff" /> <add name="X-Frame-Options" value="SAMEORIGIN" /> <add name="X-XSS-Protection" value="1; mode=block" /> <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" /> </customHeaders> </httpProtocol> </system.webServer> </configuration>

2Configure IIS Site

# Import IIS module Import-Module WebAdministration # Create application pool New-WebAppPool -Name "MinusNowITSM" Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "managedRuntimeVersion" -Value "" Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "startMode" -Value "AlwaysRunning" Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "processModel.idleTimeout" -Value "00:00:00" # Remove default website Remove-Website -Name "Default Web Site" -ErrorAction SilentlyContinue # Create website New-Website -Name "MinusNowITSM" ` -PhysicalPath "C:\MinusNow\ITSM" ` -ApplicationPool "MinusNowITSM" ` -Port 80 # Add HTTPS binding (after SSL certificate is configured) New-WebBinding -Name "MinusNowITSM" -Protocol "https" -Port 443 -HostHeader "itsm.yourdomain.com" # Enable ARR proxy Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled" -Value "True" Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "preserveHostHeader" -Value "True"

3Configure X-Forwarded Headers (Server Variables)

When running behind a reverse proxy, X-Forwarded headers pass the original client information to your application.

# Allow server variables at the server level (run once) Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' ` -Filter "system.webServer/rewrite/allowedServerVariables" ` -Name "." ` -Value @{name="HTTP_X_FORWARDED_FOR"} Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' ` -Filter "system.webServer/rewrite/allowedServerVariables" ` -Name "." ` -Value @{name="HTTP_X_FORWARDED_PROTO"} Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' ` -Filter "system.webServer/rewrite/allowedServerVariables" ` -Name "." ` -Value @{name="HTTP_X_FORWARDED_HOST"} Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' ` -Filter "system.webServer/rewrite/allowedServerVariables" ` -Name "." ` -Value @{name="HTTP_X_REAL_IP"}
Why X-Forwarded Headers?
  • X-Forwarded-For: Original client IP address
  • X-Forwarded-Proto: Original protocol (http/https)
  • X-Forwarded-Host: Original host header
  • X-Real-IP: Alternative to X-Forwarded-For for single proxy

🔒 SSL/TLS Configuration

Option A: Import PFX Certificate

# Import certificate $certPath = "C:\MinusNow\Certs\certificate.pfx" $certPassword = ConvertTo-SecureString -String "YourCertPassword" -AsPlainText -Force $cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation "Cert:\LocalMachine\My" -Password $certPassword # Bind to IIS $thumbprint = $cert.Thumbprint $binding = Get-WebBinding -Name "MinusNowITSM" -Protocol "https" $binding.AddSslCertificate($thumbprint, "My")

Option B: Using Let's Encrypt (win-acme)

# Download win-acme $wacsUrl = "https://github.com/win-acme/win-acme/releases/download/v2.2.9/win-acme.v2.2.9.1701.x64.pluggable.zip" Invoke-WebRequest -Uri $wacsUrl -OutFile "C:\MinusNow\Tools\win-acme.zip" Expand-Archive -Path "C:\MinusNow\Tools\win-acme.zip" -DestinationPath "C:\MinusNow\Tools\win-acme" # Run win-acme (interactive) cd C:\MinusNow\Tools\win-acme .\wacs.exe # Or automated mode .\wacs.exe --target iis --siteid 1 --installation iis --accepttos --emailaddress admin@yourdomain.com

⚙️ Windows Service Setup (NSSM)

1Install NSSM

# Download NSSM $nssmUrl = "https://nssm.cc/release/nssm-2.24.zip" Invoke-WebRequest -Uri $nssmUrl -OutFile "C:\MinusNow\Tools\nssm.zip" Expand-Archive -Path "C:\MinusNow\Tools\nssm.zip" -DestinationPath "C:\MinusNow\Tools" Copy-Item "C:\MinusNow\Tools\nssm-2.24\win64\nssm.exe" "C:\Windows\System32\"

2Create Windows Service

# Install service nssm install MinusNowITSM "C:\Program Files\nodejs\node.exe" nssm set MinusNowITSM AppParameters "dist\index.js" nssm set MinusNowITSM AppDirectory "C:\MinusNow\ITSM" nssm set MinusNowITSM AppEnvironmentExtra "NODE_ENV=production" nssm set MinusNowITSM DisplayName "MinusNow ITSM Application" nssm set MinusNowITSM Description "MinusNow IT Service Management Platform" nssm set MinusNowITSM Start SERVICE_AUTO_START nssm set MinusNowITSM AppStdout "C:\MinusNow\ITSM\logs\stdout.log" nssm set MinusNowITSM AppStderr "C:\MinusNow\ITSM\logs\stderr.log" nssm set MinusNowITSM AppRotateFiles 1 nssm set MinusNowITSM AppRotateBytes 10485760 # Start service Start-Service MinusNowITSM # Verify service Get-Service MinusNowITSM

✅ Verification & Testing

Check Services Status

# Check all services Get-Service MinusNowITSM, postgresql-x64-15, W3SVC | Format-Table Name, Status, StartType # Check ports Get-NetTCPConnection -LocalPort 5000,5432,80,443 -State Listen | Format-Table LocalPort, State

Test Application

# Test local access Invoke-WebRequest -Uri "http://localhost:5000/health" -UseBasicParsing # Test through IIS Invoke-WebRequest -Uri "https://itsm.yourdomain.com/" -UseBasicParsing # Check application logs Get-Content "C:\MinusNow\ITSM\logs\stdout.log" -Tail 50
✅ Browser Testing Checklist:
  1. Open https://itsm.yourdomain.com in browser
  2. Verify SSL certificate (padlock icon)
  3. Test login functionality
  4. Test all major features
  5. Check browser console for errors

🎛️ Service Management

Application Service

# Start Start-Service MinusNowITSM # Stop Stop-Service MinusNowITSM # Restart Restart-Service MinusNowITSM # Status Get-Service MinusNowITSM # View logs Get-Content "C:\MinusNow\ITSM\logs\stdout.log" -Tail 100 -Wait

IIS & Database

# IIS Start-Service W3SVC Stop-Service W3SVC iisreset /restart # PostgreSQL Start-Service postgresql-x64-15 Stop-Service postgresql-x64-15 Restart-Service postgresql-x64-15 # All services Get-Service MinusNowITSM, postgresql-x64-15, W3SVC

🔧 Troubleshooting

Application Won't Start

# Check service status Get-Service MinusNowITSM # Check logs Get-Content "C:\MinusNow\ITSM\logs\stderr.log" -Tail 50 # Check Node.js node --version # Test manually cd C:\MinusNow\ITSM node dist\index.js

IIS 502 Bad Gateway

# Check if app is running Get-Service MinusNowITSM # Test local connection Invoke-WebRequest -Uri "http://localhost:5000/" -UseBasicParsing # Check IIS logs Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\*.log" -Tail 50 # Check ARR is enabled Get-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled"

Database Connection Failed

# Check PostgreSQL service Get-Service postgresql-x64-15 # Test connection & "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U minusnow -d minusnow_itsm -h localhost -c "SELECT 1" # Check pg_hba.conf permissions Get-Content "C:\Program Files\PostgreSQL\15\data\pg_hba.conf"

SSL Certificate Issues

# List certificates Get-ChildItem Cert:\LocalMachine\My # Check IIS bindings Get-WebBinding -Name "MinusNowITSM" # Test SSL Test-NetConnection -ComputerName itsm.yourdomain.com -Port 443

Log Locations

Log TypeLocation
Application OutputC:\MinusNow\ITSM\logs\stdout.log
Application ErrorsC:\MinusNow\ITSM\logs\stderr.log
IIS LogsC:\inetpub\logs\LogFiles\
PostgreSQLC:\Program Files\PostgreSQL\15\data\log\
Windows EventsEvent Viewer → Windows Logs → Application

💾 Backup & Recovery

Automated Backup Script

# Save as C:\MinusNow\ITSM\scripts\backup.ps1 $BackupDir = "C:\MinusNow\ITSM\backups" $Date = Get-Date -Format "yyyyMMdd_HHmmss" $RetentionDays = 30 # Database backup $env:PGPASSWORD = "your_password" & "C:\Program Files\PostgreSQL\15\bin\pg_dump.exe" -U minusnow -h localhost minusnow_itsm | Out-File "$BackupDir\db_$Date.sql" -Encoding UTF8 # Compress database backup Compress-Archive -Path "$BackupDir\db_$Date.sql" -DestinationPath "$BackupDir\db_$Date.zip" Remove-Item "$BackupDir\db_$Date.sql" # Data backup Compress-Archive -Path "C:\MinusNow\ITSM\data" -DestinationPath "$BackupDir\data_$Date.zip" # Config backup Compress-Archive -Path "C:\MinusNow\ITSM\.env", "C:\MinusNow\ITSM\web.config" -DestinationPath "$BackupDir\config_$Date.zip" # Remove old backups Get-ChildItem $BackupDir -Recurse | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$RetentionDays) } | Remove-Item Write-Host "Backup completed: $Date"

Schedule Backup Task

# Create scheduled task $action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\MinusNow\ITSM\scripts\backup.ps1" $trigger = New-ScheduledTaskTrigger -Daily -At "02:00" $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount Register-ScheduledTask -TaskName "MinusNow ITSM Backup" -Action $action -Trigger $trigger -Principal $principal

Recovery Procedure

# Stop services Stop-Service MinusNowITSM # Restore database & "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U minusnow -h localhost -d minusnow_itsm -f "C:\MinusNow\ITSM\backups\db_YYYYMMDD.sql" # Restore data Expand-Archive -Path "C:\MinusNow\ITSM\backups\data_YYYYMMDD.zip" -DestinationPath "C:\MinusNow\ITSM" -Force # Start services Start-Service MinusNowITSM