-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Installers for Windows and macOS #3441
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,3 +51,7 @@ build/ | |
/benchmark_results | ||
/.update.timestamp | ||
resttest0_data | ||
|
||
# OSX | ||
.DS_Store | ||
**/.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,11 @@ const | |
defaultAdminListenAddress* = (static ValidIpAddress.init("127.0.0.1")) | ||
defaultSigningNodeRequestTimeout* = 60 | ||
|
||
when defined(windows): | ||
{.pragma: windowsOnly.} | ||
else: | ||
{.pragma: windowsOnly, hidden.} | ||
|
||
type | ||
BNStartUpCmd* {.pure.} = enum | ||
noCommand | ||
|
@@ -66,9 +71,6 @@ type | |
# status = "Displays status information about all deposits" | ||
exit = "Submits a validator voluntary exit" | ||
|
||
VCStartUpCmd* = enum | ||
VCNoCommand | ||
|
||
SNStartUpCmd* = enum | ||
SNNoCommand | ||
|
||
|
@@ -193,6 +195,12 @@ type | |
defaultValue: BNStartUpCmd.noCommand }: BNStartUpCmd | ||
|
||
of BNStartUpCmd.noCommand: | ||
runAsServiceFlag* {. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this flag would ideally not be exposed on linux - even on windows, perhaps it should be hidden given that it shouldn't be used from the command line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hidden on non-windows targets through conditional compilation. At the moment, it's not easy to completely remove the option, because the compile-time reflection of Nim doesn't provide a good way to resolve |
||
windowsOnly | ||
defaultValue: false, | ||
desc: "Run as a Windows service" | ||
name: "run-as-service" }: bool | ||
|
||
bootstrapNodes* {. | ||
desc: "Specifies one or more bootstrap nodes to use when connecting to the network" | ||
abbr: "b" | ||
|
@@ -743,25 +751,20 @@ type | |
desc: "A file specifying the authorizition token required for accessing the keymanager API" | ||
name: "keymanager-token-file" }: Option[InputFile] | ||
|
||
case cmd* {. | ||
command | ||
defaultValue: VCNoCommand }: VCStartUpCmd | ||
graffiti* {. | ||
desc: "The graffiti value that will appear in proposed blocks. " & | ||
"You can use a 0x-prefixed hex encoded string to specify " & | ||
"raw bytes" | ||
name: "graffiti" }: Option[GraffitiBytes] | ||
|
||
of VCNoCommand: | ||
graffiti* {. | ||
desc: "The graffiti value that will appear in proposed blocks. " & | ||
"You can use a 0x-prefixed hex encoded string to specify " & | ||
"raw bytes" | ||
name: "graffiti" }: Option[GraffitiBytes] | ||
|
||
stopAtEpoch* {. | ||
desc: "A positive epoch selects the epoch at which to stop" | ||
defaultValue: 0 | ||
name: "stop-at-epoch" }: uint64 | ||
stopAtEpoch* {. | ||
desc: "A positive epoch selects the epoch at which to stop" | ||
defaultValue: 0 | ||
name: "stop-at-epoch" }: uint64 | ||
|
||
beaconNodes* {. | ||
desc: "URL addresses to one or more beacon node HTTP REST APIs", | ||
name: "beacon-node" }: seq[string] | ||
beaconNodes* {. | ||
desc: "URL addresses to one or more beacon node HTTP REST APIs", | ||
name: "beacon-node" }: seq[string] | ||
|
||
SigningNodeConf* = object | ||
configFile* {. | ||
|
@@ -812,35 +815,30 @@ type | |
defaultValue: defaultSigningNodeRequestTimeout | ||
name: "request-timeout" }: int | ||
|
||
case cmd* {. | ||
command | ||
defaultValue: SNNoCommand }: SNStartUpCmd | ||
|
||
of SNNoCommand: | ||
bindPort* {. | ||
desc: "Port for the REST (BETA version) HTTP server" | ||
defaultValue: DefaultEth2RestPort | ||
defaultValueDesc: "5052" | ||
name: "bind-port" }: Port | ||
bindPort* {. | ||
desc: "Port for the REST (BETA version) HTTP server" | ||
defaultValue: DefaultEth2RestPort | ||
defaultValueDesc: "5052" | ||
name: "bind-port" }: Port | ||
|
||
bindAddress* {. | ||
desc: "Listening address of the REST (BETA version) HTTP server" | ||
defaultValue: defaultAdminListenAddress | ||
defaultValueDesc: "127.0.0.1" | ||
name: "bind-address" }: ValidIpAddress | ||
bindAddress* {. | ||
desc: "Listening address of the REST (BETA version) HTTP server" | ||
defaultValue: defaultAdminListenAddress | ||
defaultValueDesc: "127.0.0.1" | ||
name: "bind-address" }: ValidIpAddress | ||
|
||
tlsEnabled* {. | ||
desc: "Use secure TLS communication for REST (BETA version) server" | ||
defaultValue: false | ||
name: "tls" }: bool | ||
tlsEnabled* {. | ||
desc: "Use secure TLS communication for REST (BETA version) server" | ||
defaultValue: false | ||
name: "tls" }: bool | ||
|
||
tlsCertificate* {. | ||
desc: "Path to SSL certificate file" | ||
name: "tls-cert" }: Option[InputFile] | ||
tlsCertificate* {. | ||
desc: "Path to SSL certificate file" | ||
name: "tls-cert" }: Option[InputFile] | ||
|
||
tlsPrivateKey* {. | ||
desc: "Path to SSL ceritificate's private key" | ||
name: "tls-key" }: Option[InputFile] | ||
tlsPrivateKey* {. | ||
desc: "Path to SSL ceritificate's private key" | ||
name: "tls-key" }: Option[InputFile] | ||
|
||
AnyConf* = BeaconNodeConf | ValidatorClientConf | SigningNodeConf | ||
|
||
|
@@ -1006,6 +1004,9 @@ func outWalletFile*(config: BeaconNodeConf): Option[OutFile] = | |
func databaseDir*(config: AnyConf): string = | ||
config.dataDir / "db" | ||
|
||
func runAsService*(config: BeaconNodeConf): bool = | ||
config.cmd == noCommand and config.runAsServiceFlag | ||
|
||
template writeValue*(writer: var JsonWriter, | ||
value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) = | ||
writer.writeValue(string value) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,64 @@ from | |
import | ||
TopicParams, validateParameters, init | ||
|
||
when defined(windows): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we move the special windows sauce to a separate module? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, no, because the service code needs to refer and call functions from nimbus_beacon_node (not the other way around). You can work-around this with function pointers perhaps, but it would arguably make the code more convoluted and harder to follow. A more reasonable alternative might be to introduce a new module called |
||
import winlean | ||
|
||
type | ||
LPCSTR* = cstring | ||
LPSTR* = cstring | ||
|
||
SERVICE_STATUS* {.final, pure.} = object | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, stuff like this surely does not need to pollute |
||
dwServiceType*: DWORD | ||
dwCurrentState*: DWORD | ||
dwControlsAccepted*: DWORD | ||
dwWin32ExitCode*: DWORD | ||
dwServiceSpecificExitCode*: DWORD | ||
dwCheckPoint*: DWORD | ||
dwWaitHint*: DWORD | ||
|
||
SERVICE_STATUS_HANDLE* = DWORD | ||
LPSERVICE_STATUS* = ptr SERVICE_STATUS | ||
LPSERVICE_MAIN_FUNCTION* = proc (para1: DWORD, para2: LPSTR) {.stdcall.} | ||
|
||
SERVICE_TABLE_ENTRY* {.final, pure.} = object | ||
lpServiceName*: LPSTR | ||
lpServiceProc*: LPSERVICE_MAIN_FUNCTION | ||
|
||
LPSERVICE_TABLE_ENTRY* = ptr SERVICE_TABLE_ENTRY | ||
LPHANDLER_FUNCTION* = proc (para1: DWORD): WINBOOL{.stdcall.} | ||
|
||
const | ||
SERVICE_WIN32_OWN_PROCESS = 16 | ||
SERVICE_RUNNING = 4 | ||
SERVICE_STOPPED = 1 | ||
SERVICE_START_PENDING = 2 | ||
SERVICE_STOP_PENDING = 3 | ||
SERVICE_CONTROL_STOP = 1 | ||
SERVICE_CONTROL_PAUSE = 2 | ||
SERVICE_CONTROL_CONTINUE = 3 | ||
SERVICE_CONTROL_INTERROGATE = 4 | ||
SERVICE_ACCEPT_STOP = 1 | ||
NO_ERROR = 0 | ||
SERVICE_NAME = LPCSTR "NIMBUS_BEACON_NODE" | ||
|
||
var | ||
gSvcStatusHandle: SERVICE_STATUS_HANDLE | ||
gSvcStatus: SERVICE_STATUS | ||
|
||
proc reportServiceStatus*(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD) {.gcsafe.} | ||
|
||
proc StartServiceCtrlDispatcher*(lpServiceStartTable: LPSERVICE_TABLE_ENTRY): WINBOOL{. | ||
stdcall, dynlib: "advapi32", importc: "StartServiceCtrlDispatcherA".} | ||
|
||
proc SetServiceStatus*(hServiceStatus: SERVICE_STATUS_HANDLE, | ||
lpServiceStatus: LPSERVICE_STATUS): WINBOOL{.stdcall, | ||
dynlib: "advapi32", importc: "SetServiceStatus".} | ||
|
||
proc RegisterServiceCtrlHandler*(lpServiceName: LPCSTR, | ||
lpHandlerProc: LPHANDLER_FUNCTION): SERVICE_STATUS_HANDLE{. | ||
stdcall, dynlib: "advapi32", importc: "RegisterServiceCtrlHandlerA".} | ||
|
||
type | ||
RpcServer = RpcHttpServer | ||
|
||
|
@@ -1088,6 +1146,10 @@ proc onSlotStart( | |
# Check before any re-scheduling of onSlotStart() | ||
checkIfShouldStopAtEpoch(wallSlot, node.config.stopAtEpoch) | ||
|
||
when defined(windows): | ||
if node.config.runAsService: | ||
reportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0) | ||
|
||
beacon_slot.set wallSlot.toGaugeValue | ||
beacon_current_epoch.set wallSlot.epoch.toGaugeValue | ||
|
||
|
@@ -1727,7 +1789,96 @@ proc doSlashingInterchange(conf: BeaconNodeConf) {.raises: [Defect, CatchableErr | |
of SlashProtCmd.`import`: | ||
conf.doSlashingImport() | ||
|
||
proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [Defect, CatchableError].} = | ||
# Single RNG instance for the application - will be seeded on construction | ||
# and avoid using system resources (such as urandom) after that | ||
let rng = keys.newRng() | ||
|
||
case config.cmd | ||
of BNStartUpCmd.createTestnet: doCreateTestnet(config, rng[]) | ||
of BNStartUpCmd.noCommand: doRunBeaconNode(config, rng) | ||
of BNStartUpCmd.deposits: doDeposits(config, rng[]) | ||
of BNStartUpCmd.wallets: doWallets(config, rng[]) | ||
of BNStartUpCmd.record: doRecord(config, rng[]) | ||
of BNStartUpCmd.web3: doWeb3Cmd(config) | ||
of BNStartUpCmd.slashingdb: doSlashingInterchange(config) | ||
of BNStartupCmd.trustedNodeSync: | ||
let | ||
network = loadEth2Network(config) | ||
cfg = network.cfg | ||
genesis = | ||
if network.genesisData.len > 0: | ||
newClone(readSszForkedHashedBeaconState( | ||
cfg, | ||
network.genesisData.toOpenArrayByte(0, network.genesisData.high()))) | ||
else: nil | ||
|
||
waitFor doTrustedNodeSync( | ||
cfg, | ||
config.databaseDir, | ||
config.trustedNodeUrl, | ||
config.blockId, | ||
config.backfillBlocks, | ||
genesis) | ||
|
||
{.pop.} # TODO moduletests exceptions | ||
|
||
when defined(windows): | ||
proc reportServiceStatus*(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD) {.gcsafe.} = | ||
gSvcStatus.dwCurrentState = dwCurrentState | ||
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode | ||
gSvcStatus.dwWaitHint = dwWaitHint | ||
if dwCurrentState == SERVICE_START_PENDING: | ||
gSvcStatus.dwControlsAccepted = 0 | ||
else: | ||
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | ||
|
||
# TODO | ||
# We can use non-zero values for the `dwCheckPoint` parameter to report | ||
# progress during lengthy operations such as start-up and shut down. | ||
gSvcStatus.dwCheckPoint = 0 | ||
|
||
# Report the status of the service to the SCM. | ||
let status = SetServiceStatus(gSvcStatusHandle, addr gSvcStatus) | ||
debug "Service status updated", status | ||
|
||
proc serviceControlHandler(dwCtrl: DWORD): WINBOOL {.stdcall.} = | ||
case dwCtrl | ||
of SERVICE_CONTROL_STOP: | ||
# We re reporting that we plan stop the service in 10 seconds | ||
reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 10_000) | ||
bnStatus = BeaconNodeStatus.Stopping | ||
of SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE: | ||
warn "The Nimbus service cannot be paused and resimed" | ||
of SERVICE_CONTROL_INTERROGATE: | ||
# The default behavior is correct. | ||
# The service control manager will report our last status. | ||
discard | ||
else: | ||
debug "Service received an unexpected user-defined control message", | ||
msg = dwCtrl | ||
|
||
proc serviceMainFunction(dwArgc: DWORD, lpszArgv: LPSTR) {.stdcall.} = | ||
# The service is launched in a fresh thread created by Windows, so | ||
# we must initialize the Nim GC here | ||
setupForeignThreadGc() | ||
|
||
gSvcStatusHandle = RegisterServiceCtrlHandler( | ||
SERVICE_NAME, | ||
serviceControlHandler) | ||
|
||
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS | ||
gSvcStatus.dwServiceSpecificExitCode = 0 | ||
reportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0) | ||
|
||
info "Service thread started" | ||
|
||
var config = makeBannerAndConfig(clientId, BeaconNodeConf) | ||
handleStartUpCmd(config) | ||
|
||
info "Service thread stopped" | ||
reportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0) # we have to report back when we stopped! | ||
|
||
programMain: | ||
var | ||
config = makeBannerAndConfig(clientId, BeaconNodeConf) | ||
|
@@ -1762,33 +1913,18 @@ programMain: | |
quit 0 | ||
c_signal(SIGTERM, exitImmediatelyOnSIGTERM) | ||
|
||
# Single RNG instance for the application - will be seeded on construction | ||
# and avoid using system resources (such as urandom) after that | ||
let rng = keys.newRng() | ||
|
||
case config.cmd | ||
of BNStartUpCmd.createTestnet: doCreateTestnet(config, rng[]) | ||
of BNStartUpCmd.noCommand: doRunBeaconNode(config, rng) | ||
of BNStartUpCmd.deposits: doDeposits(config, rng[]) | ||
of BNStartUpCmd.wallets: doWallets(config, rng[]) | ||
of BNStartUpCmd.record: doRecord(config, rng[]) | ||
of BNStartUpCmd.web3: doWeb3Cmd(config) | ||
of BNStartUpCmd.slashingdb: doSlashingInterchange(config) | ||
of BNStartupCmd.trustedNodeSync: | ||
let | ||
network = loadEth2Network(config) | ||
cfg = network.cfg | ||
genesis = | ||
if network.genesisData.len > 0: | ||
newClone(readSszForkedHashedBeaconState( | ||
cfg, | ||
network.genesisData.toOpenArrayByte(0, network.genesisData.high()))) | ||
else: nil | ||
when defined(windows): | ||
if config.runAsService: | ||
var dispatchTable = [ | ||
SERVICE_TABLE_ENTRY(lpServiceName: SERVICE_NAME, lpServiceProc: serviceMainFunction), | ||
SERVICE_TABLE_ENTRY(lpServiceName: nil, lpServiceProc: nil) # last entry must be nil | ||
] | ||
|
||
waitFor doTrustedNodeSync( | ||
cfg, | ||
config.databaseDir, | ||
config.trustedNodeUrl, | ||
config.blockId, | ||
config.backfillBlocks, | ||
genesis) | ||
let status = StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY(addr dispatchTable[0])) | ||
if status == 0: | ||
fatal "Failed to start Windows service", errorCode = getLastError() | ||
quit 1 | ||
else: | ||
handleStartUpCmd(config) | ||
else: | ||
handleStartUpCmd(config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is "WIX" coming from? The environment?
You can do a Wix binary access check, like in the various sanity checks already in place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIX
is an environment variable created by the WIX installer. Since our installer uses only relatively basic and old WIX functionalities, it should work with any WIX version.