diff --git a/README.md b/README.md index 2eb8891..f0ff769 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,95 @@ # laconicd-network-deployment +A set of scripts to drive the mainnet-laconic stack stack orchestrator commands. + +## Background + +The mainnet-laconic stack implements a scheme for deploying multi-node networks where each node runs an instance of the stack. +The configuration of these nodes is done via a "network directory" created on each node, and by arranging to copy a genesis +transaction file from each node to a coordinating node that generates a chain genesis file. This file in turn is copied +to the remaining nodes before stack startup. In addition, the IP addresses of all peer nodes need to be configured in +each node. This scheme is implemented with the following stack orchestrator commands: + +``` +$ laconic-so --stack mainnet-laconic deploy setup --network-dir --initialize-network --chain-id --node-moniker + +$ laconic-so --stack mainnet-laconic deploy setup --network-dir --join-network --key-name + +$ laconic-so --stack mainnet-laconic deploy setup --network-dir --create-network --gentx-files + +$ laconic-so --stack mainnet-laconic deploy init --map-ports-to-host any-same --output --config "LACONIC_HOSTED_ENDPOINT=http://$:9473" + +$ laconic-so --stack mainnet-laconic deploy create --deployment-dir --spec-file --network-dir --initial-peers +``` + +The scripts in this repository provide a convenient wrapper around these commands for the testnet use case +where there can be some level of coordination and common ownership between the nodes. + +## Machine setup + +The scripts assume a set of machines with dns host names assigned with the pattern -., e.g. +testnet-a-1.testnets.servesthe.world +Testnet machines must be setup with stack orchestrator and docker installed. +The local machine (where the scripts are run) needs to be configured with `ssh-agent` and the relevant key added, such that ssh commands to connect to the testnet machines succeed without user interaction. + +## Testnet deployment steps + +``` +$ cd scripts +``` + +In the example commands below we assume the machine-name-prefix "testnet-a". + +### network.cfg + +The scripts load a set of configuration variables from a file named `network.cfg`. An example file is provided, edit to suit: +``` +$ cat network.cfg +machine_domain=testnets.servesthe.world +node_count=4 +ssh_user=laconic +``` + +### Pull stack images + +Run the 00-pull-stack-images.sh script to pull stack container images from a container registry: + +``` +$ ./01-pull-stack-images.sh testnet-a +``` + +### Initialize network directories + +``` +$ ./02-init-network-dirs.sh testnet-a +``` + +### Merge genesis files + +``` +$ ./03-merge-genesis-txns.sh testnet-a +``` + +Copy the peer list this script displays for use in the next step. + +### Create deployments + +``` +$ ./04-create-deployments.sh testnet-a +``` + +### Start stacks + +``` +$ ./deployment-command testnet-a start +``` + +### Check testnet status + +``` +$ ./deployment-command testnet-a status +$ ./deployment-command testnet-a logs +``` + +The laconic console should be available on all nodes at http:// + diff --git a/scripts/01-pull-stack-images.sh b/scripts/01-pull-stack-images.sh new file mode 100755 index 0000000..61c9be2 --- /dev/null +++ b/scripts/01-pull-stack-images.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Pull stack images for mainnet-laconic + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +source ${SCRIPT_DIR}/lib.sh + +usage="Usage: $0 " + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo ${usage} + exit 1 +fi + +if [[ -n "$2" ]]; then + image_registry_user=$2 +else + echo ${usage} + exit 1 +fi + +if [[ -n "$3" ]]; then + image_registry_token=$3 +else + echo ${usage} + exit 1 +fi + +registry_info="--image-registry git.vdb.to/cerc-io --registry-username ${image_registry_user} --registry-token ${image_registry_token}" +fetch_command="${so_command} --stack mainnet-laconic fetch-containers ${registry_info} --force-local-overwrite" +run_on_all_nodes ${machine_name_prefix} "${fetch_command}" diff --git a/scripts/02-init-network-dirs.sh b/scripts/02-init-network-dirs.sh new file mode 100755 index 0000000..ca7474a --- /dev/null +++ b/scripts/02-init-network-dirs.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo "Usage: $0 " + exit 1 +fi + +machine_domain=borgboxes.network +node_count=4 +node_network_dir=testnet-dir +chain_id=laconic_81337-6 + +echo "Initializing network dirs on all nodes" +for (( i=1 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + echo "Running initialize-network ${node_network_dir} on ${node_host_name}" + ssh laconic@${node_host_name} /home/laconic/bin/laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --initialize-network --chain-id ${chain_id} --node-moniker ${node_name} + echo "Running join-network ${node_network_dir} on ${node_host_name}" + ssh laconic@${node_host_name} /home/laconic/bin/laconic-so --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --join-network --key-name ${node_name} +done diff --git a/scripts/03-merge-genesis-txns.sh b/scripts/03-merge-genesis-txns.sh new file mode 100755 index 0000000..20a5818 --- /dev/null +++ b/scripts/03-merge-genesis-txns.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -e +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo "Usage: $0 " + exit 1 +fi + +machine_domain=borgboxes.network +node_count=4 +node_network_dir=testnet-dir +chain_id=laconic_81337-6a +p2p_port="26656" +so_command=/home/laconic/bin/laconic-so + +gentx_dir=${node_network_dir}/config/gentx +local_gentx_dir=gentx-${machine_name_prefix} + +echo "Fetch node ids and node ips for persistent-peers" +for (( i=1 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + gentx_file_name=$(ssh laconic@${node_host_name} ls /home/laconic/${gentx_dir} | head -1) + node_id=$(echo ${gentx_file_name} | sed -e 's/^gentx-//' -e 's/.json$//') + node_ip=$(dig +short ${node_host_name}) + peer=${node_id}@${node_ip}:${p2p_port} + persistent_peers+=${peer} + if [[ ${i} -lt ${node_count} ]]; then + persistent_peers+=',' + fi +done + +echo "Copying gentx from all nodes except 1" +rm -rf ${local_gentx_dir} +mkdir ${local_gentx_dir} +# Note: start at node 2 here because we're going to copy to node 1 +for (( i=2 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + echo "Copying ${gentx_dir} on ${node_name} to ${local_gentx_dir}" + scp laconic@${node_host_name}:~/${gentx_dir}/* ${local_gentx_dir} +done + +echo "Copying gentx files to node 1" +node_1_host_name=${machine_name_prefix}-1.${machine_domain} +ssh laconic@${node_1_host_name} rm -rf ${local_gentx_dir} +ssh laconic@${node_1_host_name} mkdir ${local_gentx_dir} +scp ${local_gentx_dir}/* laconic@${node_1_host_name}:~/${local_gentx_dir} + +gentx_file_list=$(ssh laconic@${node_1_host_name} ls -m ${local_gentx_dir}/*) +echo "Node 1 now has: ${gentx_file_list}" + +gentx_files=$(echo ${gentx_file_list} | tr -d ' ' | tr -d '\n') + +echo "Generate genesis on node 1" +ssh laconic@${node_1_host_name} ${so_command} --stack mainnet-laconic deploy setup --network-dir ${node_network_dir} --create-network --gentx-files ${gentx_files} + +echo "Fetching genesis file from node 1" +local_genesis_file=${local_gentx_dir}/genesis.json +scp laconic@${node_1_host_name}:~/${node_network_dir}/config/genesis.json ${local_gentx_dir} + +echo "Copying genesis file to other nodes" +# Note: start at node 2 here because we're going to copy to node 1 +for (( i=2 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + echo "Copying ${local_genesis_file} to ${node_name} to ${node_name}" + scp ${local_genesis_file} laconic@${node_host_name}:~/${node_network_dir}/config +done + +echo "Use this for persistent_peers:" +echo ${persistent_peers} + diff --git a/scripts/04-create-deployments.sh b/scripts/04-create-deployments.sh new file mode 100755 index 0000000..7514d62 --- /dev/null +++ b/scripts/04-create-deployments.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -e +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +usage="Usage: $0 " + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo ${usage} + exit 1 +fi + +if [[ -n "$2" ]]; then + peers=$2 +else + echo ${usage} + exit 1 +fi + +machine_domain=borgboxes.network +node_count=4 +node_network_dir=testnet-dir +so_command=/home/laconic/bin/laconic-so + +echo "Run deploy init on all nodes" + +spec_file_name=${machine_name_prefix}-spec.yml +deployment_dir=${machine_name_prefix}-deployment + +echo "Creating deployments on all nodes" +for (( i=1 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + laconic_console_config="LACONIC_HOSTED_ENDPOINT=http://${node_host_name}:9473" + echo "Creating deployment dir on ${node_name}" + ssh laconic@${node_host_name} ${so_command} --stack mainnet-laconic deploy init --map-ports-to-host any-same --output ${spec_file_name} --config ${laconic_console_config} + ssh laconic@${node_host_name} ${so_command} --stack mainnet-laconic deploy create --deployment-dir ${deployment_dir} --spec-file ${spec_file_name} --network-dir ${node_network_dir} --initial-peers ${peers} +done + diff --git a/scripts/delete-deployments.sh b/scripts/delete-deployments.sh new file mode 100755 index 0000000..029849b --- /dev/null +++ b/scripts/delete-deployments.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -e +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo "Usage: $0 " + exit 1 +fi + +machine_domain=borgboxes.network +node_count=4 +node_network_dir=testnet-dir +so_command=/home/laconic/bin/laconic-so + +echo "Delete deployment dirs on all nodes" + +spec_file_name=${machine_name_prefix}-spec.yml +deployment_dir=${machine_name_prefix}-deployment + +for (( i=1 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i} + node_host_name=${node_name}.${machine_domain} + echo "Deleting deployment dir on ${node_name}" + ssh laconic@${node_host_name} sudo rm -rf ${deployment_dir} +done + diff --git a/scripts/delete-network-dirs.sh b/scripts/delete-network-dirs.sh new file mode 100755 index 0000000..3e33666 --- /dev/null +++ b/scripts/delete-network-dirs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo "Usage: $0 " + exit 1 +fi + +machine_domain=borgboxes.network +node_count=4 +node_network_dir=testnet-dir +local_gentx_dir=gentx-${machine_name_prefix} + +echo "Deleting network dirs on all nodes" +for (( i=1 ; i<=$node_count ; i++ )); +do + node_name=${machine_name_prefix}-${i}.${machine_domain} + echo "Deleting ${node_network_dir} on ${node_name}" + ssh laconic@${node_name} rm -rf ${node_network_dir} ${local_gentx_dir} +done + diff --git a/scripts/deployment-command.sh b/scripts/deployment-command.sh new file mode 100755 index 0000000..d36e6ba --- /dev/null +++ b/scripts/deployment-command.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Run a specified deployment subcommand on all nodes + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +source ${SCRIPT_DIR}/lib.sh + +usage="Usage: $0 " + +if [[ -n "$1" ]]; then + machine_name_prefix=$1 +else + echo ${usage} + exit 1 +fi + +if [[ -n "$2" ]]; then + subcommand=$2 +else + echo ${usage} + exit 1 +fi + +deployment_dir=${machine_name_prefix}-deployment +deployment_command="${so_command} deployment --dir ${deployment_dir} ${subcommand}" +run_on_all_nodes ${machine_name_prefix} "${deployment_command}" diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 0000000..de89d1a --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,39 @@ + +function error_exit() { + echo "Error: $1" + exit 1 +} + +function assert_defined() { + local variable_name=$1 + if [[ ! ${!variable_name} ]]; then + error_exit "$variable_name is not defined" + fi +} + +network_config_file=./network.cfg + +if [[ ! -f ${network_config_file} ]]; then + error_exit "$network_config_file does not exist" +fi +source ${network_config_file} + +assert_defined "machine_domain" +assert_defined "node_count" +assert_defined "ssh_user" + +# Hack until we fix PATH for remote sessions +so_command=/home/laconic/bin/laconic-so + +# run_on_all_nodes(machine_name_prefix, command_to_run) +function run_on_all_nodes() { + local machine_name_prefix=$1 + local command_to_run=$2 + for (( i=1 ; i<=$node_count ; i++ )); + do + local machine_name=${machine_name_prefix}-${i}.${machine_domain} + echo "${machine_name}:" + echo "Running: ${command_to_run}" + ssh ${ssh_user}@${machine_name} ${command_to_run} + done +} diff --git a/scripts/network.cfg b/scripts/network.cfg new file mode 100644 index 0000000..58e04bd --- /dev/null +++ b/scripts/network.cfg @@ -0,0 +1,3 @@ +machine_domain=borgboxes.network +node_count=4 +ssh_user=laconic