window-pain
For those with a node app that needs to be packaged as nuget and run on Windows as a service.
Features
- Generates a nupkg containing your node app, without you having to create a nuspec file.
- Bundles all your node module dependencies into the nupkg.
- Bundles 32-bit and 64-bit versions of node.exe into the nupkg.
- Generates install.cmd file that determines the correct node.exe to use on the target server, copies the package files to a target install location, installs your app as a Windows service, and optionally starts the service.
- Generates an uninstall.cmd that stops the Windows service, uninstalls it, and deletes all nupkg files.
- Generates an installConfig.cmd that pulls configuration for your app from a git repo and installs it on the target server.
- Generates an deployment script for developer use to validate the build process has worked by deploying the app.
Caveats
- You are responsible for publishing the nuget package to whatever artifact repository you are using.
- Looks for node and other binaries in standard public repositories, but you can supercede these with your own sources if you want.
- Assumes you nuget install the package into a temporary workspace (such as a Jenkins slave workspace).
Quick Start
Add a config.windowPain property to your to your package.json file as follows. Also note use of the npm script build, which servers to encapsulate the command line that runs the window-pain build script.
{
"version": "0.0.0",
"scripts": {"build": "node node_modules/window-pain/cli.js"},
"devDependencies": {"window-pain": "latest"},
"config": {
"windowPain": {
"nuget": {"name": "AppNugetPackageName"},
"windowsService": {"name": "AppServiceName"},
"install": {
"target": {
"processorArchitecture32bit": "c:\\Program Files (x86)\\systems\\sys\\apps\\app\\code",
"processorArchitecture64bit": "c:\\Program Files\\systems\\sys\\apps\\app\\code"
},
"configTarget": {
"processorArchitecture32bit": "c:\\Program Files (x86)\\systems\\sys\\apps\\app\\_config",
"processorArchitecture64bit": "c:\\Program Files\\systems\\sys\\apps\\app\\_config"
}
}
}
}
}
Then run the following commands to build and install your app (this example assumes your app root c:\Development\app, your installation workspace is c:\Jenkins\workspace\apps, and you are on a 64 bit Windows). Note the version=1.2.3 command line argument, which is optional and if provided will override the version in package.json.
> cd c:\Development\app
> npm run build version=1.2.3
> cd c:\Jenkins\workspace\apps
> rd /S /Q AppNugetPackageName
> nuget.exe install AppNugetPackageName -Source c:\Development\app\build\nupkg -ExcludeVersion
> AppNugetPackageName\content\tools\install.cmd
> cd c:\Program Files\systems\sys\apps\app\code
> dir
> sc query "AppServiceName"
At this point the dir should show you your app root, and the sc query should show you the service with a state of running. Now unsinstall the app by running the following:
> cd c:\Jenkins\workspace\apps
> AppNugetPackageName\content\tools\install.cmd
> cd c:\Program Files\systems\sys\apps\app\code
> dir
> sc query "AppServiceName"
At this point the dir should return with something like "The system cannot find the path" because the files have been removed, and the sc query should return something like "The specified service does not exist".
Overview
Your app folder/file structure can be anything you want, except it must not have the following to folders at the app root: build, and dist.
The build process creates a folder named build, and the install process creates a folder named dist.
Your app will of course have a dependency on window-pain.
In your package.json you need to provide some configuration data for window-pain.
Build Script
The script that does all the "heavy lifting" is implemented inside the window-pain module at window-pain/cli.js. The command line syntax is:
cli.js [version]
The optional version argument is a name=value pair where name is "version" and value is a sematic version number.
If version is provided it will override the version porperty in package.json.
All you need to do to build a nuget package for you app is run the window-pain build script:
> node node_modules/window-pain/cli.js
Or, to override the package.json version:
> node node_modules/window-pain/cli.js version=1.2.3
The window-pain build script creates a folder named build in your app root folder, so your app must not have a folder named build.
The nupkg will contain your app, plus all of its production node module dependencies, plus the binary dependencies needed for any node app running as a Windows service. The nupkg will also contain three Windows command scripts:
- install.cmd, copies the node app files to a target location and installs the node app as a Windows service.
- installConfig.cmd, install application configuration files from a GIT repository.
- uninstall.cmd, uninstalls the node app (deletes the Windows service and removes the deployed files)
npm uninstall script
window-pain also has a script at window-pain/tools/npmUninstall.js to remove all your app's node module dependencies. The reason for this is to ensure the package you build only has depencencies included that are specified in your package.json.
> node node_modules/window-pain/tools/npmUninstall.js
Building on a "generic" windows server
The assumption here is that the build server has minimal tools, e.g., can run a windows batch file. So there is a script provided that will pull all the necessary tools onto the build server before running the Build Script. This script is named prebuild.bat, and is located in tools folder.
The prebuild.bat file takes one optional environment variable named primaryNuGetRepository. If set the script will look in that repository for the ConDel-Build package (which contains the means to pull all the build tools onto the generic windows server). If not specified then the script looks in the public NuGet repository.
You can run this script from any working directory because it sets the working directory to the app root (three levels up from the bat file location). This script is intended to be called from a bat file in the app itself. For example your continuous delivery/integration orchestrator would call a build.bat file such as:
REM this script file is in ./.build folder relative to app root.
@echo off
set primaryNuGetRepository=https://artifactory.mattersight.local/artifactory/api/nuget/NuGet-gallery
call ..\node_modules\window-pain\tools\prebuild.bat
package.json
In your app's package.json file include the window-pain module as a development dependency.
To be able to build your app with an npm command (e.g., npm run build), in package.json create an npm script item that references the build script, and one that references the npm uninstall script.
In the example below the npm script runs npm install to bring in all node dependencies then runs the build.js script.
{
"scripts": {
...
"_npmUninstall": "node node_modules/window-pain/tools/npmUninstall.js",
"build": "npm run _npmUninstall && npm install && node tools/build.js",
...
},
...
"devDependencies": {
...
"window-pain": "^0.0.0",
},
...
}
Building the nuget package
To generate the nuget package go to the app root and run the npm script named build:
> npm run build version=1.2.3
Publishing the nuget package
The presumtion is that the built package will be published to an artifact/package repository of some sort.
This step is to be done by whatever your continuous integration/delivery orchestrator is, or manually, or whatever.
deploy.cmd
This is a script provided as a tool for the developer to deploy a built package on outside of whatever the "production" build process/orchestrator is.
> app/build/nupkg/deploy.cmd
Installing the nuget package
nuget install
To install your app from the nuget package, first step is to get it from the artifact repository. Go to whatever your working folder is (e.g., c:\Jenkins\workspace\job
), run nuget install
:
> cd c:\Jenkins\workspace\job
> nuget.exe install app -source repository -ExcludeVersion
The nuget install
simply uncompresses the nupkg into a folder in the parent folder (e.g., c:\Jenkins\workspace\job\app
). Your app's files will all be under the app\content
folder, as is standard for nuget packages.
install.cmd
Next step is to run the install command script install.cmd
which is dynamically generated during npm run build
for the application. The target path (and some other parameters) for the installation are hardcoded in it. Running install.cmd
will will do following:
- copies workspace
app/content
folder to target directory for 32 and 64 bit systems (e.g., C:\ProgramFiles\systems\system\apps\app\code
). The target location is configured in your app's package.json file (config.windowPain.install.target). - determines which
node.exe
you need, 32 bit or 64 bit and copies the right node.exe to the dist
folder in your target location. - copies
nssm.exe
and winsw.exe
to the dist
folder in teh target location. - installs the app as a Windows service, using 'winsw.exe' or nssm.exe' (according to command line switches).
- optionally (according to command line '-Start'/'-NoStart' switch) starts the service.
Usage
install.cmd
must be started from same directory in which nuget install
command was run.
> app/content/tools/install.cmd [<switches>] [<parameters>]
See Install Application Phase for detailed description of switches and parameters
Example:
Install, don't start the service, run as local user named test with password test
app/content/tools/install.cmd -NoStart . test test
installConfig.cmd
If you are using the concept of deployable configuration, windows-pain has created an installConfig.cmd that will deploy configuration from a Git repository. For more info please refer to Install Configuration Phase.
Lifecycle of window-pain Application
Typical lifecycle of the application built using window-pain
consists of following phases:
Build Phase
Build Phase consists of several steps:
Starting the build
Build process is triggered by running cli.js
script in root directory of window-pain
package.
If window-pain is installed in the application as a root dependency, location of this script is node_modules/window-pain/cli.js
relative to root application directory.
Full command line to start the script from root application directory is:
node node_modules/window-pain/cli.js
To add ability to build the application using npm run build
command line you need to add following linea into scripts
section of application' package.json
:
...
"_npmUninstall": "node node_modules/window-pain/tools/npmUninstall.js",
"build": "npm run _npmUninstall && npm install && npm run __build",
"__build": "node node_modules/window-pain/cli.js",
...
Build configuration
Build phase configuration is based upon following config files and parameters:
0. configuration passed via command line as name=value
pairs (version is only supported name)
0. configuration passed via command line using NODE_CONFIG
parameter
0. config.windowPain
section of package.json
of window-pain
module itself
0. config.windowPain
section of package.json
of the application
All the above sources of configuration are merged (earlier in above list supercede later in the list) to get the final configuration. So configuration passed from command line has the highest priority, while package config has the lowest priority.
Examples
To set the version of the app (including the version of the nuget package) use the command line name=value pair:
npm run build version=1.2.3
To override any configuration parameter in the package.json files you may use the NODE_CONFIG
command line option.
Following examples represents how to disable strict SSL on the download of binary dependencies:
> npm run build -- --NODE_CONFIG="{\"download\":{\"strictSSL\":true}}"
Cleanup Step
Description
Content of following directories is cleaned during this step (relatively to project's root directory):
build/dependencies/
build/resources/
build/nupkg
Configuration
There are no configurable parameters for this step.
Download Step
Description
window-pain
allows to automatically download files from external source to be used in a package.
By default it is configured to download following binaries:
filename | version | Description |
---|
node.exe | 4.2.6 | Node.js main binary version |
nuget.exe | 3.3.0 | Utility to manage Nuget packages |
winsw.exe | 1.18 | utility used to manage Windows services |
nssm.exe | 2.24 | alternate utility to manage Windows services |
Configuration
All the options including URLs and filenames are configured in download
section of the config.
Parameters:
overwrite
[boolean=true]
If true
, overwrite file if already exist, if false
, skip download if file already exists.
strictSSL
[boolean=true]
If false
, disables server certificates check. Useful when servers certificates are expired or not recoginzed by Node. Setting it to false lowers security and may result in downloading of maliciouos software. Use wtih care.
dest
string
Base directory to save downloaded files
files
Object[]
Array of file download definitions
Each file download definition consists of following properties:
name
- string
- filename to save downloaded file (overrides server-provided filename);path
- string
- subdirectory of base directory to download controlled by dest parameter to place downloaded file;url
|| urls
- string
|| string[]
- single url to download ot array of urls (try to download from first one, if fails then try second etc)
Example:
"download": {
"overwrite": true,
"strictSSL": true,
"dest": "build/bin",
"files": [
{ "url": "https://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/1.18/winsw-1.18-bin.exe",
"path": "winsw",
"name": "winsw.exe"
},
{ "url": "https://nodejs.org/dist/v4.2.6/win-x64/node.exe",
"path": "v4.2.6/win-x64"
"name": "node.exe"
},
{ "url": "https://nodejs.org/dist/v4.2.6/win-x86/node.exe",
"path": "v4.2.6/win-x86"
"name": "node.exe"
},
{ "url": "https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe",
"path": "nuget"
"name": "nuget.exe"
},
{ "url": "https://nssm.cc/release/nssm-2.24.zip",
"path": "nssm"
"name": "nssm-2.24.zip"
}
]
},
Unzip Step
Description
During this step window-pain
automatically uncompresses zipped files
Configuration
Options for this phase are configured in unzip
section of the config.
src
string
base directory for zipped files
dst
string
base directory to output unzipped files
files
Object[]
Array of object containing source and target pathnames.
Properties of each object are following:
src
- source pathnamedst
- destination pathname
Example:
"unzip": {
"src": "build/bin",
"dest": "build/bin",
"files": [
{
"src": "nssm/nssm-2.24.zip",
"dest": "nssm"
}
]
},
Update Version Step
Description
window-pain
allows autoincrement version patch number in package.json
file (disabled by default).
Configuration
Options for this phase are configured in version
section of the config.
autoincrement
[boolean=false]
Setting this parameter to true
to enables automatic increment version patch number on each build.
Note. As this operation modifies the source files (package.json
), the changes must be later commit
-ed and push
-ed to Git repository or revert
-ed.
backup
string
Directory to backup package.json
file before modification.
Example
"version": {
"autoincrement": false,
"backup": "build/backup"
},
Prepare to install npm
dependencies
Description
This step is inteded to prepare environment to install dependencies listed as production in project's package.json
from NPM repositroy into separate directory.
This step copies project's package.json
into build/dependencies/
directory.
Configuration
There are no configurable parameters for this step.
Install npm
dependencies
Description
This step is inteded to install dependencies listed as production in project's package.json
from NPM repository into separate directory.
This step executes npm install --production
in build/dependencies/
directory.
Configuration
There are no configurable parameters for this step.
Prepare resource files step
Description
This step is intended to prepare different scripts and configuration files to be bundled into nupkg for use on further lifecycle phases. Main purpose is to set proper file locations and options.
There are two types of files handled by this step:
- configuration files - (i.e. Package.nuspec)
- start script - i.e. entry point for main script
Each file is prepared basing on template. Templates are written using EJS syntax. Inside the template following namespaces are accessible:
appPkg
- application' package.json
pkgPkg
- window-pain
module' package.json
config
- actual configuration merged from command line, window-pain
package.json
, application' package.json
.
All the templates are located in subdirectories of app/templates/
directory:
app/templates/resources/
- configuration files (Package.nuspec
)app/templates/tools/
- scripts (install.cmd
, pullConfig.cmd
, installConfig.cmd
, uninstall.cmd
)
Result files are used by next steps of the build phase and expected to be located in corresponding build/templates/resources
and build/template/tools
directories.
Following file are handled:
Package.nuspec
- Nuspec file for the nuget utility. It defines all the files to be put into nupkg and their corresponding source and target locations;install.cmd
- start script to run main installation scripts;pullConfig.cmd
- start script to run scripts to pull additional config;installConfig.cmd
- start script to run scripts to install additional config;uninstall.cmd
- start script to run main uninstall scripts.
Configuration
Options for this phase are configured in resources
section of the config.
This section has the only property templates
containing array of the definitions to prepare the files.
Each definition consists of following properties:
src
- string
- pathname of the templatetarget
- string
- pathname of resulting file
NOTE.
Some other sections of config, application package.json
and window-pain
package.json
maybe be used inside a templates.
Examples are:
files
section is used when generating Package.nuspec
install
section is used when generating install.cmd
, installConfig.cmd
, pullConfig.cmd
, uninstall.cmd
. Please, refer to Install Application Phase and Install Config Phase section of this readme for more info.version
parameter is used when generating Package.nuspec
As whole content of application package.json
and window-pain
module' package.json
are passed to the templates, any other parameters also may be involved in template processing.
Example
"resources": {
"templates": [
{
"src": "app/templates/resources/Package.nuspec",
"target": "build/templates/resources/Package.nuspec"
},
{
"src": "app/templates/tools/install.cmd",
"target": "build/templates/tools/install.cmd"
},
{
"src": "app/templates/tools/installConfig.cmd",
"target": "build/templates/tools/installConfig.cmd"
},
{
"src": "app/templates/tools/pullConfig.cmd",
"target": "build/templates/tools/pullConfig.cmd"
},
{
"src": "app/templates/tools/uninstall.cmd",
"target": "build/templates/tools/uninstall.cmd"
}
]
},
Pack nupkg step
Description
This step is intended to bundle all the files prepared on previous steps into single nupkg.
To make a nupkg it runs nuget.exe
utility downloaded in Download Step.
nuget.exe
utility builds nupkg package basing on the parameters passed through command line and configuration in file Package.nuspec
.
For more info on nuget.exe
utility please refer to official Nuget docs: https://docs.nuget.org/
Configuration
name
string
Path to output nupkg file.
nupkgPath
string
Path to output nupkg file.
exe
string
Path to nuget.exe
utility.
nuspec
files
object[]
Array of file definition objects
Properties of each object are following:
src
- source pathname files patterntarget
- target pathnameexclude
- exclude pathname pattern
For more info on these parameters please refer to official Nuget docs: https://docs.nuget.org/
Parameter files
is inserted into Package.nuspec
during Prepare resource files step and is used by nuget.exe
utility.
exclude
string[]
List of additional files/directories to exclude.
excludeDevDependencies
boolean
DEPRECATED
Exclude all files listed in devDependencies
section of application's package.json
.
Usage not recommended.
Example
"nuget": {
"name": null,
"nupkgPath": "build\\nupkg",
"exe": "build\\bin\\nuget\\nuget.exe",
"nuspec": "build\\templates\\resources\\Package.nuspec",
"files": [
{
"src": "..\\..\\..\\**\\*.*",
"target": "content",
"exclude": "..\\..\\..\\.git\\**;..\\..\\..\\build\\**;..\\..\\..\\log\\**;..\\..\\..\\tmp\\**;..\\..\\..\\coverage\\**;..\\..\\..\\test\\**;..\\..\\..\\node_modules\\**"
},
{
"src": "..\\..\\..\\build\\dependencies\\node_modules\\**\\*.*",
"target": "content\\node_modules"
},
{
"src": "..\\..\\..\\build\\bin\\**\\*.*",
"target": "content\\dist"
},
{
"src": "..\\..\\..\\build\\templates\\tools\\**\\*.*",
"target": "content\\tools"
},
{
"src": "..\\..\\..\\node_modules\\window-pain\\app\\source\\**\\*.*",
"target": "content"
}
],
"exclude": [],
"excludeDevDependencies": false
},
Install Nupkg Phase
By default, the nupkg generated by window-pain
is located in build\nupkg
directory (relative to application root)
To install nupkg following command to be run from command line:
nuget.exe install CompanyName.%app_name% -source "%app_src_dir%\build\nupkg" -ExcludeVersion
Example for ApplicationName:
nuget.exe install CompanyName.ApplicationName -source c:\app\ApplicationName\build\nupkg" -ExcludeVersion
This command will unpack the nupkg into current directory.
Pull Config Phase
Prerequisites: Nupkg is installed into local directory
To pull the additional config following command to be run from command line:
pullConfig.cmd" <env_name> <git_repo_name> <repo_url> <repo_branch_or_tag> <repo_username> <repo_password>
- env_name - 'environment name` i.e. subdirectory to use inside the repository
- repo_name - Git repository name
- repo_url - Git repository URL
- repo_branch_or_tag - Git repository Branch or Tag name
- repo_username - Git repository username
- repo_password - Git repository password
CALL "%nuget_install_dir%\content\tools\pullConfig.cmd" "%env_name%" "%git_repo_name%" "github.com/CompanyName/config.git" "%git_branch_or_tag%" "%git_username%" %repo_pass%
install.cmd
utility is used to install the application to target directory, install it as windows service and optionally to start.
install.cmd
Usage:
install.cmd [<switch> ...] [<domain> <user> <password>]
install.cmd
has following optional switches:
-NoStart
|| -Start
- windows service autostart option (default is to start). When using installConfig.cmd to install additional config, you must use -NoStart
to avoid starting with inappropriate config.-winsw
|| -nssm
- windows service control utility (deafault is winsw
)-robocopy
|| -xcopy
- file copy utility to use (default is robocopy
)
install.cmd
has following optional parameters:
- - optional, user domain for the service
- - optional, user name for the service
- - optional, user password for the service
Example:
CALL "%nuget_install_dir%\content\tools\install.cmd"
Configuration
During Prepare resource files step following parameters from install
section of the config are used to set destination paths for the application
"install": {
"target": {
"processorArchitecture32bit": "c:\\target",
"processorArchitecture64bit": "c:\\target"
},
"configTarget": {
"processorArchitecture32bit": "c:\\target",
"processorArchitecture64bit": "c:\\target"
}
}
Description
During Install Config Phase you can install additional config (usually pulled from Git repository during Pull Config Phase) which may have priority over default application config. Also, this config will not be touched by install.cmd
and uninstall.cmd
scripts and and will survive application upgrade.
Example for the locations are:
- Code directory:
C:\Program Files\apps\approot\code
- App directory:
C:\Program Files\apps\approot
- Outside config directory (must not be touched by install/uninstall):
C:\Program Files\apps\approot\_config
- Inside config directory (must be created/removed during install/uninstall):
C:\Program Files\apps\approot\code\_config
Root application directory may also depend on 32/64 system type (e.g. c:\Program Files (x86)\
and C:\Program Files\
and is controlled by install.target.*
and install.configTarget.*
parameters of the config (see below).
installConfig.cmd
command line parameters
installConfig.cmd" <env_name> <git_repo_name> <repo_url> <repo_branch_or_tag> <repo_username> <repo_password>
- env_name - 'environment name` i.e. subdirectory to use inside the repository
- repo_name - Git repository name
- repo_url - Git repository URL
- repo_branch_or_tag - Git repository Branch or Tag name
- repo_username - Git repository username
- repo_password - Git repository password
CALL "%nuget_install_dir%\content\tools\installConfig.cmd" "%env_name%" "%git_repo_name%" "github.com/CompanyName/config.git" "%git_branch_or_tag%" "%git_username%" %repo_pass%
Configuration
During Prepare resource files step following parameters from install
section of the config are used to set destination paths for the application
"install": {
"target": {
"processorArchitecture32bit": "c:\\target",
"processorArchitecture64bit": "c:\\target"
},
"configTarget": {
"processorArchitecture32bit": "c:\\target",
"processorArchitecture64bit": "c:\\target"
}
}
Usage Phase
...
Uninstall Application Phase
Following command tries to stop the serves and removes the installed application.
CALL "%nuget_install_dir%\content\tools\uninstall.cmd"
This command does not removes the installed nupkg.
Remove Nupkg Phase
To remove nupkg you need to manually delete its directory.
Following command may be used:
RMDIR /S /Q "%nuget_install_dir%"
Change Log
version 0.7.23
- Switch from node v6.11.2 to node v6.11.3
Prior versions
- Determines latest patch level from the configured artifact repository
- Removed prebuild.bat script
- Fixed working directory location in deploy.cmd script
- Added check for lock on target directory to install.cmd
- Added script for manual install
- Minor change to install script display
- Improved _uninstall.cmd
- Robocopy more strict; return code must be 1 rather than less than 8
- Clean up the _install and _uninstall scripts
- Renaming install.cmd to install.code.cmd, same pattern for others
- Forgot to include pull.config.cmd
- Changing references to new script names
- Check that service is not stopped before stopping. This enables hard failure on error.
- Improve error checking on install scripts
- Switch administrator privileges check from NET SESSION to OPENFILES
- Stop logging npm install output
- Build using version 0.0.0 when unable to connect with artifact repository