From e3ccf6a79f1e183c3784e0e73e1823a1301561ab Mon Sep 17 00:00:00 2001 From: Stefano Franz Date: Tue, 20 Nov 2018 13:38:44 +0000 Subject: [PATCH] Add Docker image as output of build (#4223) --- docker/build.gradle | 64 +++++++ docker/src/bash/example-generate-testnet.sh | 19 ++ docker/src/bash/example-join-generic-cz.sh | 27 +++ docker/src/bash/generate-config.sh | 124 +++++++++++++ docker/src/bash/run-corda.sh | 10 ++ docker/src/config/starting-node.conf | 39 +++++ docker/src/docker/Dockerfile | 70 ++++++++ .../kotlin/net.corda.core/ConfigExporter.kt | 84 +++++++++ docs/source/building-a-cordapp-index.rst | 1 + docs/source/corda-nodes-index.rst | 1 + docs/source/docker-image.rst | 163 ++++++++++++++++++ settings.gradle | 1 + 12 files changed, 603 insertions(+) create mode 100644 docker/build.gradle create mode 100755 docker/src/bash/example-generate-testnet.sh create mode 100755 docker/src/bash/example-join-generic-cz.sh create mode 100755 docker/src/bash/generate-config.sh create mode 100755 docker/src/bash/run-corda.sh create mode 100644 docker/src/config/starting-node.conf create mode 100644 docker/src/docker/Dockerfile create mode 100644 docker/src/main/kotlin/net.corda.core/ConfigExporter.kt create mode 100644 docs/source/docker-image.rst diff --git a/docker/build.gradle b/docker/build.gradle new file mode 100644 index 0000000000..0bb9d2afe1 --- /dev/null +++ b/docker/build.gradle @@ -0,0 +1,64 @@ +evaluationDependsOn(":node:capsule") +buildscript { + repositories { + mavenLocal() + mavenCentral() + jcenter() + } + dependencies { + classpath 'com.bmuschko:gradle-docker-plugin:3.4.4' + } +} + + +import com.bmuschko.gradle.docker.DockerRemoteApiPlugin +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage + +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +apply plugin: 'kotlin' +apply plugin: DockerRemoteApiPlugin +apply plugin: 'application' +// We need to set mainClassName before applying the shadow plugin. +mainClassName = 'net.corda.core.ConfigExporterMain' +apply plugin: 'com.github.johnrengelman.shadow' + + +dependencies{ + compile project(':node') +} + +shadowJar { + baseName = 'config-exporter' + classifier = null + version = null + zip64 true +} + + +task buildDockerFolder(dependsOn: [":node:capsule:buildCordaJAR", shadowJar]) { + doLast { + def cordaJar = project(":node:capsule").buildCordaJAR.archivePath + project.copy { + into new File(project.buildDir, "docker-temp") + from "src/bash/run-corda.sh" + from cordaJar + from shadowJar.archivePath + from "src/config/starting-node.conf" + from "src/bash/generate-config.sh" + from "src/docker/Dockerfile" + rename(cordaJar.name, "corda.jar") + rename(shadowJar.archivePath.name, "config-exporter.jar") + } + } +} + +task buildOfficialDockerImage(type: DockerBuildImage, dependsOn: [buildDockerFolder]) { + final String runTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + //if we are a snapshot, append a timestamp + //if we are a release, append RELEASE + final String suffix = project.version.toString().toLowerCase().contains("snapshot") ? runTime : "RELEASE" + inputDir = new File(project.buildDir, "docker-temp") + tags = ["corda/corda-${project.version.toString().toLowerCase()}:${suffix}", "corda/corda-${project.version.toString().toLowerCase()}:latest"] +} \ No newline at end of file diff --git a/docker/src/bash/example-generate-testnet.sh b/docker/src/bash/example-generate-testnet.sh new file mode 100755 index 0000000000..069fdd8ff8 --- /dev/null +++ b/docker/src/bash/example-generate-testnet.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +docker run -ti \ + -e MY_PUBLIC_ADDRESS="corda-node.example.com" \ + -e ONE_TIME_DOWNLOAD_KEY="bbcb189e-9e4f-4b27-96db-134e8f592785" \ + -e LOCALITY="London" -e COUNTRY="GB" \ + -v $(pwd)/docker/config:/etc/corda \ + -v $(pwd)/docker/certificates:/opt/corda/certificates \ + corda/corda-4.0-snapshot:latest config-generator --testnet + +docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v $(pwd)/docker/config:/etc/corda \ + -v $(pwd)/docker/certificates:/opt/corda/certificates \ + -v $(pwd)/docker/persistence:/opt/corda/persistence \ + -v $(pwd)/docker/logs:/opt/corda/logs \ + -v $(pwd)/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \ + corda/corda-4.0-snapshot:latest \ No newline at end of file diff --git a/docker/src/bash/example-join-generic-cz.sh b/docker/src/bash/example-join-generic-cz.sh new file mode 100755 index 0000000000..3d42b788b3 --- /dev/null +++ b/docker/src/bash/example-join-generic-cz.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +##in this example the doorman will be running on the host machine on port 8080 +##so the container must be launched with "host" networking +docker run -ti --net="host" \ + -e MY_LEGAL_NAME="O=EXAMPLE,L=Berlin,C=DE" \ + -e MY_PUBLIC_ADDRESS="corda.example-hoster.com" \ + -e NETWORKMAP_URL="https://map.corda.example.com" \ + -e DOORMAN_URL="https://doorman.corda.example.com" \ + -e NETWORK_TRUST_PASSWORD="trustPass" \ + -e MY_EMAIL_ADDRESS="cordauser@r3.com" \ + -v $(pwd)/docker/config:/etc/corda \ + -v $(pwd)/docker/certificates:/opt/corda/certificates \ + corda/corda-4.0-snapshot:latest config-generator --generic + +##set memory to 2gb max, and 2cores max +docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v $(pwd)/docker/config:/etc/corda \ + -v $(pwd)/docker/certificates:/opt/corda/certificates \ + -v $(pwd)/docker/persistence:/opt/corda/persistence \ + -v $(pwd)/docker/logs:/opt/corda/logs \ + -v $(pwd)/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \ + -p 10200:10200 \ + -p 10201:10201 \ + corda/corda-4.0-snapshot:latest \ No newline at end of file diff --git a/docker/src/bash/generate-config.sh b/docker/src/bash/generate-config.sh new file mode 100755 index 0000000000..dc71bf4b71 --- /dev/null +++ b/docker/src/bash/generate-config.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash + + +die() { + printf '%s\n' "$1" >&2 + exit 1 +} + +show_help(){ + + echo "usage: generate-config <--testnet>|<--generic>" + echo -e "\t --testnet is used to generate config and certificates for joining TestNet" + echo -e "\t --generic is used to generate config and certificates for joining an existing Corda Compatibility Zone" + +} + +function generateTestnetConfig() { + RPC_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) \ + DB_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) \ + MY_PUBLIC_ADDRESS=${MY_PUBLIC_ADDRESS} \ + MY_P2P_PORT=${MY_P2P_PORT} \ + MY_RPC_PORT=${MY_RPC_PORT} \ + MY_RPC_ADMIN_PORT=${MY_RPC_ADMIN_PORT} \ + NETWORKMAP_URL='https://map.testnet.corda.network' \ + DOORMAN_URL='https://doorman.testnet.corda.network' \ + java -jar config-exporter.jar "TEST-NET-COMBINE" "node.conf" "/opt/corda/starting-node.conf" "${CONFIG_FOLDER}/node.conf" +} + +function generateGenericCZConfig(){ + : ${NETWORKMAP_URL:? '$NETWORKMAP_URL, the Compatibility Zone to join must be set as environment variable'} + : ${DOORMAN_URL:? '$DOORMAN_URL, the Doorman to use when joining must be set as environment variable'} + : ${MY_LEGAL_NAME:? '$MY_LEGAL_NAME, the X500 name to use when joining must be set as environment variable'} + : ${MY_EMAIL_ADDRESS:? '$MY_EMAIL_ADDRESS, the email to use when joining must be set as an environment variable'} + : ${NETWORK_TRUST_PASSWORD=:? '$NETWORK_TRUST_PASSWORD, the password to the network store to use when joining must be set as environment variable'} + + if [[ ! -f ${CERTIFICATES_FOLDER}/${TRUST_STORE_NAME} ]]; then + die "Network Trust Root file not found" + fi + + RPC_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) \ + DB_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) \ + MY_PUBLIC_ADDRESS=${MY_PUBLIC_ADDRESS} \ + MY_P2P_PORT=${MY_P2P_PORT} \ + MY_RPC_PORT=${MY_RPC_PORT} \ + MY_RPC_ADMIN_PORT=${MY_RPC_ADMIN_PORT} \ + java -jar config-exporter.jar "GENERIC-CZ" "/opt/corda/starting-node.conf" "${CONFIG_FOLDER}/node.conf" + + java -Djava.security.egd=file:/dev/./urandom -Dcapsule.jvm.args="${JVM_ARGS}" -jar /opt/corda/bin/corda.jar \ + initial-registration \ + --base-directory=/opt/corda \ + --config-file=/etc/corda/node.conf \ + --network-root-truststore-password=${NETWORK_TRUST_PASSWORD} \ + --network-root-truststore=${CERTIFICATES_FOLDER}/${TRUST_STORE_NAME} +} + +function downloadTestnetCerts() { + if [[ ! -f ${CERTIFICATES_FOLDER}/certs.zip ]]; then + : ${ONE_TIME_DOWNLOAD_KEY:? '$ONE_TIME_DOWNLOAD_KEY must be set as environment variable'} + : ${LOCALITY:? '$LOCALITY (the locality used when registering for Testnet) must be set as environment variable'} + : ${COUNTRY:? '$COUNTRY (the country used when registering for Testnet) must be set as environment variable'} + curl -L -d "{\"x500Name\":{\"locality\":\"${LOCALITY}\", \"country\":\"${COUNTRY}\"}, \"configType\": \"INSTALLSCRIPT\", \"include\": { \"systemdServices\": false, \"cordapps\": false, \"cordaJar\": false, \"cordaWebserverJar\": false, \"scripts\": false} }" \ + -H 'Content-Type: application/json' \ + -X POST "https://testnet.corda.network/api/user/node/generate/one-time-key/redeem/$ONE_TIME_DOWNLOAD_KEY" \ + -o "${CERTIFICATES_FOLDER}/certs.zip" + fi + rm -rf ${CERTIFICATES_FOLDER}/*.jks + unzip ${CERTIFICATES_FOLDER}/certs.zip +} + +GENERATE_TEST_NET=0 +GENERATE_GENERIC=0 + +while :; do + case $1 in + -h|-\?|--help) + show_help # Display a usage synopsis. + exit + ;; + -t|--testnet) + if [[ ${GENERATE_GENERIC} = 0 ]]; then + GENERATE_TEST_NET=1 + else + die 'ERROR: cannot generate config for multiple networks' + fi + ;; + -g|--generic) + if [[ ${GENERATE_TEST_NET} = 0 ]]; then + GENERATE_GENERIC=1 + else + die 'ERROR: cannot generate config for multiple networks' + fi + ;; + --) # End of all options. + shift + break + ;; + -?*) + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) # Default case: No more options, so break out of the loop. + break + esac + shift +done + + +: ${TRUST_STORE_NAME="network-root-truststore.jks"} +: ${JVM_ARGS='-Xmx4g -Xms2g -XX:+UseG1GC'} + + +if [[ ${GENERATE_TEST_NET} == 1 ]] +then + : ${MY_PUBLIC_ADDRESS:? 'MY_PUBLIC_ADDRESS must be set as environment variable'} + downloadTestnetCerts + generateTestnetConfig +elif [[ ${GENERATE_GENERIC} == 1 ]] +then + : ${MY_PUBLIC_ADDRESS:? 'MY_PUBLIC_ADDRESS must be set as environment variable'} + generateGenericCZConfig +else + show_help + die "No Valid Configuration requested" +fi + diff --git a/docker/src/bash/run-corda.sh b/docker/src/bash/run-corda.sh new file mode 100755 index 0000000000..c47545d9c5 --- /dev/null +++ b/docker/src/bash/run-corda.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +: ${JVM_ARGS='-XX:+UseG1GC'} + +JVM_ARGS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap "${JVM_ARGS} + +if [[ ${JVM_ARGS} == *"Xmx"* ]]; then + echo "WARNING: the use of the -Xmx flag is not recommended within docker containers. Use the --memory option passed to the container to limit heap size" +fi + +java -Djava.security.egd=file:/dev/./urandom -Dcapsule.jvm.args="${JVM_ARGS}" -jar /opt/corda/bin/corda.jar --base-directory=/opt/corda --config-file=/etc/corda/node.conf \ No newline at end of file diff --git a/docker/src/config/starting-node.conf b/docker/src/config/starting-node.conf new file mode 100644 index 0000000000..b78e38b53d --- /dev/null +++ b/docker/src/config/starting-node.conf @@ -0,0 +1,39 @@ +myLegalName=${MY_LEGAL_NAME} +p2pAddress=${MY_PUBLIC_ADDRESS}":"${MY_P2P_PORT} +rpcSettings { + address="0.0.0.0:"${MY_RPC_PORT} + adminAddress="0.0.0.0:"${MY_RPC_ADMIN_PORT} +} + +security { + authService { + dataSource { + type=INMEMORY + users=[ + { + password=${RPC_PASSWORD} + permissions=[ + ALL + ] + user=rpcUser + } + ] + } + } +} +networkServices : { + doormanURL = ${DOORMAN_URL} + networkMapURL = ${NETWORKMAP_URL} +} + +detectPublicIp = false +dataSourceProperties { + dataSource { + password=${DB_PASSWORD} + url="jdbc:h2:file:/opt/corda/persistence/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000" + user="sa" + } + dataSourceClassName="org.h2.jdbcx.JdbcDataSource" +} + +emailAddress = ${MY_EMAIL_ADDRESS} diff --git a/docker/src/docker/Dockerfile b/docker/src/docker/Dockerfile new file mode 100644 index 0000000000..5340c68638 --- /dev/null +++ b/docker/src/docker/Dockerfile @@ -0,0 +1,70 @@ +FROM azul/zulu-openjdk:8u192 + +RUN apt-get update && apt-get -y upgrade && apt-get -y install bash curl unzip + +# Create dirs +RUN mkdir -p /opt/corda/cordapps +RUN mkdir -p /opt/corda/persistence +RUN mkdir -p /opt/corda/certificates +RUN mkdir -p /opt/corda/drivers +RUN mkdir -p /opt/corda/logs +RUN mkdir -p /opt/corda/bin +RUN mkdir -p /opt/corda/additional-node-infos +RUN mkdir -p /etc/corda + +# Create corda user +RUN addgroup corda && \ + useradd corda -g corda -m -d /opt/corda + +WORKDIR /opt/corda + +ENV CORDAPPS_FOLDER="/opt/corda/cordapps" +ENV PERSISTENCE_FOLDER="/opt/corda/persistence" +ENV CERTIFICATES_FOLDER="/opt/corda/certificates" +ENV DRIVERS_FOLDER="/opt/corda/drivers" +ENV CONFIG_FOLDER="/etc/corda" + +ENV MY_P2P_PORT=10200 +ENV MY_RPC_PORT=10201 +ENV MY_RPC_ADMIN_PORT=10202 + +RUN chown -R corda:corda /opt/corda +RUN chown -R corda:corda /etc/corda + +##CORDAPPS FOLDER +VOLUME ["/opt/corda/cordapps"] +##PERSISTENCE FOLDER +VOLUME ["/opt/corda/persistence"] +##CERTS FOLDER +VOLUME ["/opt/corda/certificates"] +##OPTIONAL JDBC DRIVERS FOLDER +VOLUME ["/opt/corda/drivers"] +##LOG FOLDER +VOLUME ["/opt/corda/logs"] +##ADDITIONAL NODE INFOS FOLDER +VOLUME ["/opt/corda/additional-node-infos"] +##CONFIG LOCATION +VOLUME ["/etc/corda"] + + +##CORDA JAR +ADD --chown=corda:corda corda.jar /opt/corda/bin/corda.jar +##CONFIG MANIPULATOR JAR +ADD --chown=corda:corda config-exporter.jar /opt/corda/config-exporter.jar +##CONFIG GENERATOR SHELL SCRIPT +ADD --chown=corda:corda generate-config.sh /opt/corda/bin/config-generator +##CORDA RUN SCRIPT +ADD --chown=corda:corda run-corda.sh /opt/corda/bin/run-corda +##BASE CONFIG FOR GENERATOR +ADD --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf +##SET EXECUTABLE PERMISSIONS +RUN chmod +x /opt/corda/bin/config-generator +RUN chmod +x /opt/corda/bin/run-corda + +ENV PATH=$PATH:/opt/corda/bin + +EXPOSE $MY_P2P_PORT +EXPOSE $MY_RPC_PORT + +USER "corda" +CMD ["run-corda"] diff --git a/docker/src/main/kotlin/net.corda.core/ConfigExporter.kt b/docker/src/main/kotlin/net.corda.core/ConfigExporter.kt new file mode 100644 index 0000000000..05b38531bc --- /dev/null +++ b/docker/src/main/kotlin/net.corda.core/ConfigExporter.kt @@ -0,0 +1,84 @@ +@file:JvmName("ConfigExporterMain") + +package net.corda.core + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValueFactory +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.validation.internal.Validated +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.parseAsNodeConfiguration +import net.corda.nodeapi.internal.config.toConfig +import net.corda.nodeapi.internal.config.toConfigValue +import java.io.File + +class ConfigExporter { + fun combineTestNetWithOurConfig(testNetConf: String, ourConf: String, outputFile: String) { + var ourParsedConfig = ConfigFactory.parseFile(File(ourConf)) + val testNetParsedConfig = ConfigFactory.parseFile(File(testNetConf)) + ourParsedConfig = ourParsedConfig.withValue("keyStorePassword", testNetParsedConfig.getValue("keyStorePassword")) + ourParsedConfig = ourParsedConfig.withValue("myLegalName", testNetParsedConfig.getValue("myLegalName")) + ourParsedConfig = ourParsedConfig.withValue("trustStorePassword", testNetParsedConfig.getValue("trustStorePassword")) + ourParsedConfig = ourParsedConfig.withValue("emailAddress", testNetParsedConfig.getValue("emailAddress")) + File(outputFile).writer().use { fileWriter -> + val finalConfig = ourParsedConfig.parseAsNodeConfigWithFallback().value().toConfig() + var configToWrite = ConfigFactory.empty() + ourParsedConfig.entrySet().sortedBy { it.key }.forEach { configEntry -> + //use all keys present in "ourConfig" but get values from "finalConfig" + val keyWithoutQuotes = configEntry.key.replace("\"", "") + println("creating config key: $keyWithoutQuotes with value: ${finalConfig.getValue(keyWithoutQuotes)}") + configToWrite = configToWrite.withValue(keyWithoutQuotes, finalConfig.getValue(keyWithoutQuotes)) + } + fileWriter.write(configToWrite.root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(false))) + } + } + + fun buildGenericCZConfig(ourConf: String, outputFile: String){ + val ourParsedConfig = ConfigFactory.parseFile(File(ourConf)) + File(outputFile).writer().use { fileWriter -> + val finalConfig = ourParsedConfig.parseAsNodeConfigWithFallback().value().toConfig() + var configToWrite = ConfigFactory.empty() + ourParsedConfig.entrySet().sortedBy { it.key }.forEach { configEntry -> + //use all keys present in "ourConfig" but get values from "finalConfig" + val keyWithoutQuotes = configEntry.key.replace("\"", "") + println("creating config key: $keyWithoutQuotes with value: ${finalConfig.getValue(keyWithoutQuotes)}") + configToWrite = configToWrite.withValue(keyWithoutQuotes, finalConfig.getValue(keyWithoutQuotes)) + } + fileWriter.write(configToWrite.root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(false))) + } + } +} + +fun Config.parseAsNodeConfigWithFallback(): Validated { + val referenceConfig = ConfigFactory.parseResources("reference.conf") + val nodeConfig = this + .withValue("baseDirectory", ConfigValueFactory.fromAnyRef("/opt/corda")) + .withFallback(referenceConfig) + .resolve() + return nodeConfig.parseAsNodeConfiguration() +} + +fun main(args: Array) { + val configExporter = ConfigExporter() + + val command = args[0] + + when (command) { + "TEST-NET-COMBINE" -> { + val testNetConf = args[1] + val ourConf = args[2] + val outputFile = args[3] + configExporter.combineTestNetWithOurConfig(testNetConf, ourConf, outputFile) + } + "GENERIC-CZ" -> { + val ourConf = args[1] + val outputFile = args[2] + configExporter.buildGenericCZConfig(ourConf, outputFile) + } + else -> { + throw IllegalArgumentException("Unknown command: $command") + } + } +} \ No newline at end of file diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index 1586941e93..45f0c9d90e 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -15,6 +15,7 @@ CorDapps upgrade-notes upgrading-cordapps secure-coding-guidelines + flow-overriding corda-api flow-cookbook cheat-sheet diff --git a/docs/source/corda-nodes-index.rst b/docs/source/corda-nodes-index.rst index 23fce7574e..a1803d14ab 100644 --- a/docs/source/corda-nodes-index.rst +++ b/docs/source/corda-nodes-index.rst @@ -6,6 +6,7 @@ Nodes node-structure generating-a-node + docker-image running-a-node deploying-a-node corda-configuration-file diff --git a/docs/source/docker-image.rst b/docs/source/docker-image.rst new file mode 100644 index 0000000000..4b866a376f --- /dev/null +++ b/docs/source/docker-image.rst @@ -0,0 +1,163 @@ +Official Corda Docker Image +=========================== + +Running a Node connected to a Compatibility Zone in Docker +---------------------------------------------------------- + +.. note:: Requirements: A valid node.conf and a valid set of certificates - (signed by the CZ) + +In this example, the certificates are stored at ``/home/user/cordaBase/certificates``, the node configuration is in ``/home/user/cordaBase/config/node.conf`` and the CorDapps to run are in ``/home/TeamCityOutput/cordapps`` + +.. code-block:: shell + + docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v /home/user/cordaBase/config:/etc/corda \ + -v /home/user/cordaBase/certificates:/opt/corda/certificates \ + -v /home/user/cordaBase/persistence:/opt/corda/persistence \ + -v /home/user/cordaBase/logs:/opt/corda/logs \ + -v /home/TeamCityOutput/cordapps:/opt/corda/cordapps \ + -p 10200:10200 \ + -p 10201:10201 \ + corda/corda-4.0-snapshot:latest + +As the node runs within a container, several mount points are required + +1. CorDapps - CorDapps must be mounted at location ``/opt/corda/cordapps`` +2. Certificates - certificates must be mounted at location ``/opt/corda/certificates`` +3. Config - the node config must be mounted at location ``/etc/corda/node.config`` +4. Logging - all log files will be written to location ``/opt/corda/logs`` + +If using the H2 database + +5. Persistence - the folder to hold the H2 database files must be mounted at location ``/opt/corda/persistence`` + +Running a Node connected to a Bootstrapped Network +-------------------------------------------------- + +.. note:: Requirements: A valid node.conf, a valid set of certificates, and an existing network-parameters file + +In this example, we have previously generated a network-parameters file using the bootstrapper tool, which is stored at ``/home/user/sharedFolder/network-parameters`` + + +.. code-block:: shell + + docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v /home/user/cordaBase/config:/etc/corda \ + -v /home/user/cordaBase/certificates:/opt/corda/certificates \ + -v /home/user/cordaBase/persistence:/opt/corda/persistence \ + -v /home/user/cordaBase/logs:/opt/corda/logs \ + -v /home/TeamCityOutput/cordapps:/opt/corda/cordapps \ + -v /home/user/sharedFolder/node-infos:/opt/corda/additional-node-infos \ + -v /home/user/sharedFolder/network-parameters:/opt/corda/network-parameters \ + -p 10200:10200 \ + -p 10201:10201 \ + corda/corda-4.0-snapshot:latest + +There is a new mount ``/home/user/sharedFolder/node-infos:/opt/corda/additional-node-infos`` which is used to hold the ``nodeInfo`` of all the nodes within the network. +As the node within the container starts up, it will place it's own nodeInfo into this directory. This will allow other nodes also using this folder to see this new node. + + +Generating Configs and Certificates +=================================== + +It is possible to utilize the image to automatically generate a sensible minimal configuration for joining an existing Corda network. + +Joining TestNet +--------------- + +.. note:: Requirements: A valid registration for TestNet and a one-time code for joining TestNet. + +.. code-block:: shell + + docker run -ti \ + -e MY_PUBLIC_ADDRESS="corda-node.example.com" \ + -e ONE_TIME_DOWNLOAD_KEY="bbcb189e-9e4f-4b27-96db-134e8f592785" \ + -e LOCALITY="London" -e COUNTRY="GB" \ + -v /home/user/docker/config:/etc/corda \ + -v /home/user/docker/certificates:/opt/corda/certificates \ + corda/corda-4.0-snapshot:latest config-generator --testnet + +``$MY_PUBLIC_ADDRESS`` will be the public address that this node will be advertised on. +``$ONE_TIME_DOWNLOAD_KEY`` is the one-time code provided for joining TestNet. +``$LOCALITY`` and ``$COUNTRY`` must be set to the values provided when joining TestNet. + +When the container has finished executing ``config-generator`` the following will be true + +1. A skeleton, but sensible minimum node.conf is present in ``/home/user/docker/config`` +2. A set of certificates signed by TestNet in ``/home/user/docker/certificates`` + +It is now possible to start the node using the generated config and certificates + +.. code-block:: shell + + docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v /home/user/docker/config:/etc/corda \ + -v /home/user/docker/certificates:/opt/corda/certificates \ + -v /home/user/docker/persistence:/opt/corda/persistence \ + -v /home/user/docker/logs:/opt/corda/logs \ + -v /home/user/corda/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \ + -p 10200:10200 \ + -p 10201:10201 \ + corda/corda-4.0-snapshot:latest + + +Joining An Existing Compatibility Zone +-------------------------------------- + +.. note:: Requirements: A Compatibility Zone, the Zone Trust Root and authorisation to join said Zone. + +It is possible to use the image to automate the process of joining an existing Zone as detailed `here `__ + +The first step is to obtain the Zone Trust Root, and place it within a directory. In the below example, the Trust Root is stored at ``/home/user/docker/certificates/network-root-truststore.jks``. +It is possible to configure the name of the Trust Root file by setting the ``TRUST_STORE_NAME`` environment variable in the container. + +.. code-block:: shell + + docker run -ti --net="host" \ + -e MY_LEGAL_NAME="O=EXAMPLE,L=Berlin,C=DE" \ + -e MY_PUBLIC_ADDRESS="corda.example-hoster.com" \ + -e NETWORKMAP_URL="https://map.corda.example.com" \ + -e DOORMAN_URL="https://doorman.corda.example.com" \ + -e NETWORK_TRUST_PASSWORD="trustPass" \ + -e MY_EMAIL_ADDRESS="cordauser@r3.com" \ + -v /home/user/docker/config:/etc/corda \ + -v /home/user/docker/certificates:/opt/corda/certificates \ + corda/corda-4.0-snapshot:latest config-generator --generic + + +Several environment variables must also be passed to the container to allow it to register: + +1. ``MY_LEGAL_NAME`` - The X500 to use when generating the config. This must be the same as registered with the Zone. +2. ``MY_PUBLIC_ADDRESS`` - The public address to advertise the node on. +3. ``NETWORKMAP_URL`` - The address of the Zone's network map service (this should be provided to you by the Zone). +4. ``DOORMAN_URL`` - The address of the Zone's doorman service (this should be provided to you by the Zone). +5. ``NETWORK_TRUST_PASSWORD`` - The password to the Zone Trust Root (this should be provided to you by the Zone). +6. ``MY_EMAIL_ADDRESS`` - The email address to use when generating the config. This must be the same as registered with the Zone. + +There are some optional variables which allow customisation of the generated config: + +1. ``MY_P2P_PORT`` - The port to advertise the node on (defaults to 10200). If changed, ensure the container is launched with the correct published ports. +2. ``MY_RPC_PORT`` - The port to open for RPC connections to the node (defaults to 10201). If changed, ensure the container is launched with the correct published ports. + +Once the container has finished performing the initial registration, the node can be started as normal + +.. code-block:: shell + + docker run -ti \ + --memory=2048m \ + --cpus=2 \ + -v /home/user/docker/config:/etc/corda \ + -v /home/user/docker/certificates:/opt/corda/certificates \ + -v /home/user/docker/persistence:/opt/corda/persistence \ + -v /home/user/docker/logs:/opt/corda/logs \ + -v /home/user/corda/samples/bank-of-corda-demo/build/nodes/BankOfCorda/cordapps:/opt/corda/cordapps \ + -p 10200:10200 \ + -p 10201:10201 \ + corda/corda-4.0-snapshot:latest + diff --git a/settings.gradle b/settings.gradle index 26a9cfde4d..dece86ebff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include 'client:jfx' include 'client:mock' include 'client:rpc' include 'djvm' +include 'docker' include 'djvm:cli' include 'webserver' include 'webserver:webcapsule'