Current section

Files

Jump to
relx priv templates extended_bin_windows_ps
Raw

priv/templates/extended_bin_windows_ps

#! /usr/bin/pwsh
# This script handles managing an Erlang node as a Windows service.
#
# Commands provided:
#
# * install - install the release as a Windows service
# * start - start the service and Erlang node
# * stop - stop the service and Erlang node
# * restart - run the stop command and start command
# * uninstall - uninstall the service and kill a running node
# * ping - check if the node is running
# * console - start the Erlang release in a `werl` Windows shell
# * attach - connect to a running node and open an interactive console
# * remote_console - alias for attach
# * list - display a listing of installed Erlang services
# * usage - display available commands
param (
[string] $Command
)
# Terminate on error
$ErrorActionPreference = "Stop"
# Set variables that describe the release
$rel_name = '{{ rel_name }}'
$rel_vsn = '{{ rel_vsn }}'
$erts_vsn = '{{ erts_vsn }}'
$erl_opts = '{{ erl_opts }}'
# Ensure we have PSScriptRoot
if (!(Test-Path variable:global:PSScriptRoot)) {
# Support powershell 2.0
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
}
# Ensure we have PSCommandPath
if (!(Test-Path variable:global:PSCommandPath)) {
# Support powershell 2.0
$PSCommandPath = $MyInvocation.PSCommandPath
}
$PSCriptName = Split-Path -Leaf -Path $PSCommandPath
# Import psutil helper functions
. $PSScriptRoot\psutil.ps1
# Set the root release directory based on the location of this batch file
$rootdir = Split-Path -Parent -Path $PSScriptRoot
$rel_dir = "$rootdir\releases\$rel_vsn"
$erts_root, $erts_dir = Find-ERTS -RelRoot $rootdir -Vsn $erts_vsn
$sys_config = Find-FormatConfig -RelDir $rel_dir -File 'sys.config'
$vm_args = Find-FormatConfig -RelDir $rel_dir -File 'vm.args'
$boot_script = Find-BootScript -RelDir $rel_dir -RelName $rel_name
$service_name = "${rel_name}_${rel_vsn}"
$nodetool = "$rootdir\bin\nodetool"
$bindir = "$erts_dir\bin"
$werl = "$bindir\werl.exe"
$erl = "$bindir\erl.exe"
$erlsrv = "$bindir\erlsrv.exe"
$epmd = "$bindir\epmd.exe"
$escript = "$bindir\escript.exe"
# Added in OTP-23
$erl_call = "$bindir\erl_call.exe"
# if RELX_RPC_TIMEOUT is set then use that
# otherwise check for NODETOOL_TIMEOUT and convert to seconds
if (Test-Path env:RELX_RPC_TIMEOUT) {
$relx_rpc_timeout = $env:RELX_RPC_TIMEOUT
}
elseif (Test-Path env:NODETOOL_TIMEOUT) {
# will exit the script if NODETOOL_TIMEOUT isn't a number
$relx_rpc_timeout = $env:NODETOOL_TIMEOUT / 1000
}
else {
$relx_rpc_timeout = 60
}
$extensions = @("{{ extensions }}" -split '[|]' | Where-Object { $_ -ne 'undefined' })
# Run extension script
function RunExtension() {
param(
[string]$Name
)
# Manifest xxx_extension as local variable
"{{ extension_declarations }}" -split ";" | ForEach-Object { Invoke-Expression "`$$_" }
# Manifest value of xxx_extension as $script
$script = Invoke-Expression "`$${Name}_extension"
# Execute
& "$PSScriptRoot\$script" @args
}
# Extract node type and name from vm.args
$node_type, $node_name, $hostname = Get-NodeAndHost $vm_args
# Extract the target cookie
# Do this before relx_get_nodename so we can use it and not create a ~/.erlang.cookie
$cookie = Get-Cookie $vm_args
# Collect additional (allowed) VM args into erl_opts for erlsrv/start_erl
$opts = Get-Content $vm_args `
| Select-String '^(#|-name|-sname|-setcookie|-noinput|-noshell)' -NotMatch `
| ForEach-Object { $_.line } `
| Where-Object { $_.length -gt 0 }
$erl_opts += "$opts"
# If a start.boot file is not present, copy one from the named .boot file
if (!(Test-Path "$rel_dir\start.boot")) {
Copy-Item "$rel_dir\$rel_name.boot" "$rel_dir\start.boot" | Out-Null
}
# List of supported commands, help, etc
$commands = @{
'help' = @{
'ShortHelp' = 'Show help';
'LongHelp' = 'Shows help for <command>';
'Usage' = '<command>';
'Invoke' = 'Usage';
'PassArgs' = $true
` };
'install' = @{
'ShortHelp' = 'Install the service';
'LongHelp' = 'Installs release <version> as a windows service';
'Usage' = '<version>';
'Invoke' = 'Install';
'PassArgs' = $true
` };
'uninstall' = @{
'ShortHelp' = 'Uninstall the service';
'LongHelp' = 'Uninstalls release <version> as a windows service';
'Usage' = '<version>';
'Invoke' = 'Uninstall';
'PassArgs' = $true
};
'start' = @{
'ShortHelp' = 'Start the service';
'LongHelp' = 'Starts the installed windows service';
'Usage' = '';
'Invoke' = 'Start-SVC';
'Aliases' = @('daemon')
};
'stop' = @{
'ShortHelp' = 'Stop the service';
'LongHelp' = 'Stops the installed windows service';
'Usage' = '';
'Invoke' = 'Stop-SVC'
};
'restart' = @{
'ShortHelp' = 'Restart the service';
'LongHelp' = 'Restarts the installed windows service';
'Usage' = '';
'Invoke' = @('Stop-SVC','Start-SVC')
};
'upgrade' = @{
'ShortHelp' = 'Upgrade the running service';
'LongHelp' = 'Upgrades the service to release <version>';
'Usage' = '<version> [--no-permanent]';
'Invoke' = 'Relup';
'PassArgs' = $true
};
'downgrade' = @{
'ShortHelp' = 'Downgrade the running service';
'LongHelp' = 'Downgrades the service to release <version>';
'Usage' = '<version>';
'Invoke' = 'Relup';
'PassArgs' = $true
};
'console' = @{
'ShortHelp' = 'Start the release with an interactive shell';
'LongHelp' = 'Starts release VERSION with an interactive shell';
'Usage' = '';
'Invoke' = 'Console'
};
'foreground' = @{
'ShortHelp' = 'Start the release with a non-interactive shell';
'LongHelp' = 'Starts the release with a non-interactive shell';
'Usage' = '';
'Invoke' = 'Foreground'
};
'ping' = @{
'ShortHelp' = 'Print pong if the node is alive';
'LongHelp' = 'Checks if the service (or console) is running';
'Usage' = '';
'Invoke' = 'Ping'
};
'list' = @{
'ShortHelp' = 'List installed Erlang services';
'LongHelp' = 'Lists all Erlang services installed on this machine';
'Usage' = '';
'Invoke' = 'List'
};
'attach' = @{
'ShortHelp' = 'Connect remote shell to running node';
'LongHelp' = 'Connects to running service or console shell';
'Usage' = '';
'Invoke' = 'Attach';
'Aliases' = @('remote_console', 'remote', 'remsh', 'daemon_attach')
};
'rpc' = @{
'ShortHelp' = 'Applies the specified function and returns the result';
'LongHelp' = "Applies the specified function and returns the result.`n" `
+ "Mod must be specified. However, start and [] are assumed`n" `
+ "for unspecified Fun and Args, respectively. Args is to`n" `
+ "be in the same format as for erlang:apply/3 in ERTS."
'Usage' = '<mod> [<func> [<args>]]';
'Invoke' = @('PingOrExit', 'Rpc');
'PassArgs' = $true
};
'eval' = @{
'ShortHelp' = 'Executes a sequence of Erlang expressions';
'LongHelp' = 'Executes a sequence of Erlang expressions, separated by`n' `
+ 'comma (,) and ended with a full stop (.)'
'Usage' = '[<exprs>]';
'Invoke' = @('PingOrExit', 'Eval');
'PassArgs' = $true
};
'versions' = @{
'ShortHelp' = 'Print versions of the release available';
'LongHelp' = 'Print versions of the release available'
'Usage' = '';
'Invoke' = 'Versions'
}
}
# Build easy aliases
$aliases = @{}
$commands.GetEnumerator() |
Where-Object { $_.Value.ContainsKey('Aliases') } |
ForEach-Object { $target = $_.Key; $_.Value.Aliases | Foreach-Object { $aliases += @{ $_ = $target } }; }
# Output usage
function Usage() {
param(
[string]$Command
)
switch ($Command) {
{$commands.ContainsKey($Command)} {
$cmd = $commands.item($_)
"Usage: $PSCriptName $Command $($cmd.Usage)"
""
"$($cmd.LongHelp)"
""
}
{$aliases.ContainsKey($Command)} {
$cmd = $commands.item($aliases.item($_))
"Usage: $PSCriptName $Command $($cmd.Usage)"
""
"$($cmd.LongHelp)"
""
}
{$extensions -contains $_} { RunExtension $_ help }
Default {
"Usage: $($MyInvocation.MyCommand.Name) <command> <args>"
"Commands:"
$commands.keys | Sort-Object | ForEach-Object {
@{"$_" = $commands.item($_).ShortHelp }
} | Format-Table -HideTableHeaders
}
}
}
# Install the service
function Install() {
param(
[string]$Version
)
if (![string]::IsNullOrWhiteSpace($Version)) {
# relup and reldown
Relup $Version
return
}
# Install the service
$args = "$erl_opts -setcookie $cookie ++ -rootdir `"`"$erts_root`"`" -reldir `"`"$rootdir\releases`"`""
$start_erl = "$erts_dir\bin\start_erl.exe"
$description = "Erlang node ${node_name}${hostname} in $rootdir"
$params = @('add', $service_name, $node_type, "${node_name}${hostname}", `
'-c', $description, `
'-w', $rootdir, `
'-m', $start_erl, `
#'-d', 'reuse', ` # Add this to debug startup
'-e', "ERL_LIBS=$rootdir\lib", `
'-stopaction', 'init:stop().', `
'-args', $args)
& $erlsrv $params
}
# Uninstall the Windows service
function Uninstall() {
param(
[string]$Version
)
if (![string]::IsNullOrWhiteSpace($Version)) {
# relup and reldown
Relup $Version
return
}
# Uninstall the Windows service
& $erlsrv remove $service_name
& $epmd -kill
}
# Start the Windows service
function Start-SVC() {
& $erlsrv start $service_name
}
# Stop the Windows service
function Stop-SVC() {
& $erlsrv stop $service_name
}
# Relup and reldown
function Relup() {
param(
[string]$Version
)
PingOrExit
$params = @("$rootdir/bin/install_upgrade.escript", 'install', "{'$rel_name', `"$node_type`", '${node_name}${hostname}', '$cookie'}", $Version)
& $escript $params @args
}
# Start a console
function Console() {
# Set the ERL_LIBS environment variable
$env:ERL_LIBS = "$rootdir\lib"
# Start the release in an `erl` shell
$params = @($erl_opts, '-boot', $boot_script)
if (![string]::IsNullOrEmpty($sys_config)) { $params += '-config', $sys_config }
if (![string]::IsNullOrEmpty($vm_args)) { $params += '-args_file', $vm_args }
& $werl $params
}
# Start a non-interactive console
function Foreground() {
# Set the ERL_LIBS environment variable
$env:ERL_LIBS = "$rootdir\lib"
# Start the release in an `erl` shell
$params = @($erl_opts, '-boot', $boot_script, '-noinput', '+Bd')
if (![string]::IsNullOrEmpty($sys_config)) { $params += '-config', $sys_config }
if (![string]::IsNullOrEmpty($vm_args)) { $params += '-args_file', $vm_args }
& $erl $params
}
# Execute erl_call.exe
function ErlCall()
{
param(
[string]$Type
)
$params = @($node_type, $node_name, '-R', '-c', $cookie, '-timeout', $relx_rpc_timeout)
switch($Type) {
'-a' { & $erl_call $params '-a' "`"$args`"" }
'-e' { "$args" | & $erl_call $params '-e' }
Default {
Write-Error "Unsupported erlcall ($_)"
}
}
}
# Execute escript.exe nodetool
function Nodetool()
{
$params = @($node_type, $node_name, '-setcookie', $cookie)
& $escript $nodetool $params @args
}
# Execute an RPC call
function Rpc()
{
if (Test-Path $erl_call) {
ErlCall '-a' @args
}
else {
Nodetool rpc @args
}
}
function Eval()
{
if (Test-Path $erl_call) {
ErlCall '-e' @args
}
else {
Nodetool eval @args
}
}
# Ping node and error (exit) if not running
function PingOrExit() {
$result = (Rpc erlang is_alive)
if ($result -ne "true") {
Write-Error "Node is not running! ($result)"
}
}
# Ping the running node
function Ping() {
PingOrExit
"pong"
}
# List installed Erlang services
function List() {
& $erlsrv 'list' $service_name
}
# Attach to a running node
function Attach() {
PingOrExit
$params = @('-boot', "$rel_dir\start_clean", `
'-remsh', "${node_name}${hostname}", `
$node_type, "console_${node_name}_$(Get-Random -maximum 99999)", `
'-setcookie', $cookie)
& $werl $params
}
# Print versions of the release available
function Versions()
{
PingOrExit
$params = @("$rootdir\bin\install_upgrade.escript", 'versions', "{'$rel_name', `"$node_type`", '${node_name}${hostname}', '$cookie'}")
& $escript $params @args
}
# Run a command
function RunCommand()
{
param(
[string]$Name
)
$cmd = $commands.item($Name)
if ($cmd.PassArgs) { $pass_args = $args }
$cmd.Invoke | ForEach-Object {
& $_ @pass_args
}
}
# Finally, here is where we run the command
switch ($Command)
{
"" { Usage }
{$commands.ContainsKey($_)} {
RunCommand $_ @args
}
{$aliases.ContainsKey($Command)} {
RunCommand $aliases.item($_) @args
}
{$extensions -contains $_} {
RunExtension $_ @args
}
Default {
Write-Warning "Unknown command: `"$_`""
Usage
}
}