Browse Source

Merge pull request #2410 from ahhda/automated-infrastructure

Automated network deployment scritps
Mokhtar Naamani 3 years ago
parent
commit
525e9c9618

+ 5 - 0
devops/infrastructure/.gitignore

@@ -0,0 +1,5 @@
+# Ignore files created by deployment scripts
+bash-config.cfg
+inventory
+data-*
+chain_spec_output.txt

+ 35 - 0
devops/infrastructure/README.md

@@ -0,0 +1,35 @@
+## Setup
+
+### Configuring the AWS CLI
+We’re going to use the AWS CLI to access AWS resources from the command line. 
+
+Follow [the official directions](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) for your system.
+
+Once the AWS CLI is installed, configure a profile
+
+`aws configure --profile joystream-user`
+
+### Create a key pair
+Change profile and region parameters according to your configuration
+```
+aws ec2 create-key-pair --key-name joystream-key --profile joystream-user --region us-east-1 --query 'KeyMaterial' --output text > joystream-key.pem
+```
+
+Set the permissions for the key pair 
+
+`chmod 400 joystream-key.pem`
+
+### Install Ansible
+On Mac run the command:
+* `brew install ansible`
+
+Follow [the official installation guide](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) for your system.
+
+# How to run
+Edit the file `bash-config.cfg` and update parameters like AWS_KEY_PAIR_NAME, KEY_PATH
+Run the `deploy-infra.sh` script to deploy the infrastructure
+
+```
+cd devops/infrastructure
+./deploy-infra.sh
+```

+ 8 - 0
devops/infrastructure/ansible.cfg

@@ -0,0 +1,8 @@
+[defaults]
+host_key_checking = False
+remote_user = ubuntu
+# Use the YAML callback plugin.
+stdout_callback = yaml
+# Use the stdout_callback when running ad-hoc commands.
+bin_ansible_callbacks = True
+interpreter_python = /usr/bin/python3

+ 22 - 0
devops/infrastructure/bash-config.sample.cfg

@@ -0,0 +1,22 @@
+#### PARAMETERS USED BY AWS
+
+STACK_NAME=joystream-node
+REGION=us-east-1
+CLI_PROFILE=joystream-user
+KEY_PATH="/Users/joystream/Joystream/joystream-key.pem"
+AWS_KEY_PAIR_NAME="joystream-key"
+EC2_INSTANCE_TYPE=t2.xlarge
+
+# Set a prebuilt AMI if required
+EC2_AMI_ID="ami-064f99551235fb1ac"
+
+#### PARAMETERS USED BY ANSIBLE
+
+LOCAL_CODE_PATH="~/Joystream/joystream"
+NETWORK_SUFFIX=7891
+
+GIT_REPO="https://github.com/Joystream/joystream.git"
+BRANCH_NAME=sumer
+
+# If true will build LOCAL_CODE_PATH otherwise will pull from GIT_REPO:BRANCH_NAME
+BUILD_LOCAL_CODE=false

+ 14 - 0
devops/infrastructure/build-code.yml

@@ -0,0 +1,14 @@
+---
+
+- name: Get latest Joystream code and build it
+  hosts: all
+  tasks:
+    - name: Get code from local or git repo
+      include_role:
+        name: common
+        tasks_from: "{{ 'get-code-local' if build_local_code|bool else 'get-code-git' }}"
+
+    - name: Run setup and build
+      include_role:
+        name: common
+        tasks_from: run-setup-build

+ 27 - 0
devops/infrastructure/chain-spec-configuration.yml

@@ -0,0 +1,27 @@
+---
+# Configure chain spec and start joystream-node service on the servers
+
+- name: Create and copy the chain-spec file
+  hosts: all
+  gather_facts: no
+
+  tasks:
+    - name: Generate chain-spec file and data keys either on localhost or admin server
+      include_role:
+        name: common
+        tasks_from: chain-spec-node-keys
+      vars:
+        local_or_admin: "{{ groups['build'][0] if run_on_admin_server|bool else 'localhost' }}"
+        admin_code_dir: "{{ remote_code_path if run_on_admin_server|bool else local_dir }}"
+
+- name: Copy secret, auth and start joystream-node service for validators
+  hosts: validators
+
+  roles:
+    - validators
+
+- name: Configure RPC service and start it
+  hosts: rpc
+
+  roles:
+    - rpc

+ 80 - 0
devops/infrastructure/deploy-infra.sh

@@ -0,0 +1,80 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$1" ]; then
+  echo "ERROR: Configuration file not passed"
+  echo "Please use ./deploy-infra.sh PATH/TO/CONFIG to run this script"
+  exit 1
+else
+  echo "Using $1 file for config"
+  source $1
+fi
+
+ACCOUNT_ID=$(aws sts get-caller-identity --profile $CLI_PROFILE --query Account --output text)
+
+NEW_STACK_NAME="${STACK_NAME}-${ACCOUNT_ID}"
+
+DATA_PATH="data-$NEW_STACK_NAME"
+
+INVENTORY_PATH="$DATA_PATH/inventory"
+
+if [ $ACCOUNT_ID == None ]; then
+    echo "Couldn't find Account ID, please check if AWS Profile $CLI_PROFILE is set"
+    exit 1
+fi
+
+if [ ! -f "$KEY_PATH" ]; then
+    echo "Key file not found at $KEY_PATH"
+    exit 1
+fi
+
+# Deploy the CloudFormation template
+echo -e "\n\n=========== Deploying main.yml ==========="
+aws cloudformation deploy \
+  --region $REGION \
+  --profile $CLI_PROFILE \
+  --stack-name $NEW_STACK_NAME \
+  --template-file main.yml \
+  --no-fail-on-empty-changeset \
+  --capabilities CAPABILITY_NAMED_IAM \
+  --parameter-overrides \
+    EC2InstanceType=$EC2_INSTANCE_TYPE \
+    KeyName=$AWS_KEY_PAIR_NAME \
+    EC2AMI=$EC2_AMI_ID
+
+# If the deploy succeeded, get the IP, create inventory and configure the created instances
+if [ $? -eq 0 ]; then
+  VALIDATORS=$(aws cloudformation list-exports \
+    --profile $CLI_PROFILE \
+    --query "Exports[?starts_with(Name,'${NEW_STACK_NAME}PublicIp')].Value" \
+    --output text | sed 's/\t\t*/\n/g')
+
+  RPC_NODES=$(aws cloudformation list-exports \
+    --profile $CLI_PROFILE \
+    --query "Exports[?starts_with(Name,'${NEW_STACK_NAME}RPCPublicIp')].Value" \
+    --output text | sed 's/\t\t*/\n/g')
+
+  BUILD_SERVER=$(aws cloudformation list-exports \
+    --profile $CLI_PROFILE \
+    --query "Exports[?starts_with(Name,'${NEW_STACK_NAME}BuildPublicIp')].Value" \
+    --output text | sed 's/\t\t*/\n/g')
+
+  mkdir -p $DATA_PATH
+
+  echo -e "[build]\n$BUILD_SERVER\n\n[validators]\n$VALIDATORS\n\n[rpc]\n$RPC_NODES" > $INVENTORY_PATH
+
+  if [ -z "$EC2_AMI_ID" ]
+  then
+    echo -e "\n\n=========== Configuring the node servers ==========="
+    ansible-playbook -i $INVENTORY_PATH --private-key $KEY_PATH build-code.yml --extra-vars "branch_name=$BRANCH_NAME git_repo=$GIT_REPO build_local_code=$BUILD_LOCAL_CODE"
+  fi
+
+  echo -e "\n\n=========== Configuring the Build server ==========="
+  ansible-playbook -i $INVENTORY_PATH --private-key $KEY_PATH setup-admin.yml \
+    --extra-vars "local_dir=$LOCAL_CODE_PATH build_local_code=$BUILD_LOCAL_CODE"
+
+  echo -e "\n\n=========== Configuring the chain spec file ==========="
+  ansible-playbook -i $INVENTORY_PATH --private-key $KEY_PATH chain-spec-configuration.yml \
+    --extra-vars "local_dir=$LOCAL_CODE_PATH network_suffix=$NETWORK_SUFFIX data_path=data-$NEW_STACK_NAME"
+fi

+ 18 - 0
devops/infrastructure/group_vars/all

@@ -0,0 +1,18 @@
+---
+# Variables applicable to all hosts
+
+branch_name: sumer
+git_repo: "https://github.com/Joystream/joystream.git"
+
+local_dir: ~/Joystream/joystream
+
+# Generates random number between 1000..9999
+network_suffix: "{{ 10000 | random(1000) }}"
+
+data_path: ./data
+chain_spec_path: "{{ data_path }}/chainspec.json"
+raw_chain_spec_path: "{{ data_path }}/chainspec-raw.json"
+remote_code_path: "/home/ubuntu/joystream"
+remote_chain_spec_path: "{{ remote_code_path }}/chainspec.json"
+run_on_admin_server: true
+build_local_code: false

+ 47 - 0
devops/infrastructure/library/json_modify.py

@@ -0,0 +1,47 @@
+#!/usr/bin/python
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def main():
+    fields = {
+        "chain_spec_path": {"required": True, "type": "str"},
+        "file_content": {"required": False, "type": "str" },
+        "prefix": {"required": False, "type": "str" },
+        "all_nodes": {"required": False, "type": "dict" }
+    }
+    module = AnsibleModule(argument_spec=fields)
+    prefix = module.params["prefix"]
+    chain_spec_path = module.params["chain_spec_path"]
+    all_nodes = module.params["all_nodes"]
+
+    with open(chain_spec_path) as f:
+        data = json.load(f)
+
+    response = {
+        "name": f'{data["name"]} {prefix}',
+        "id": f'{data["id"]}_{prefix}',
+        "protocolId": f'{data["protocolId"]}{prefix}'
+    }
+
+    boot_node_list = data["bootNodes"]
+    for key in all_nodes:
+        if "validators" in all_nodes[key]["group_names"]:
+            public_key = all_nodes[key]["subkey_output"]["stderr"]
+            boot_node_list.append(f"/ip4/{key}/tcp/30333/p2p/{public_key}")
+
+    telemetry_endpoints = data["telemetryEndpoints"]
+    telemetry_endpoints.append([
+        "/dns/telemetry.joystream.org/tcp/443/x-parity-wss/%2Fsubmit%2F", 0])
+
+    response["bootNodes"] = boot_node_list
+    response["telemetryEndpoints"] = telemetry_endpoints
+
+    data.update(response)
+    with open(chain_spec_path, 'w') as outfile:
+        json.dump(data, outfile, indent=4)
+    module.exit_json(changed=False, result=response)
+
+if __name__ == '__main__':
+    main()

+ 179 - 0
devops/infrastructure/main.yml

@@ -0,0 +1,179 @@
+AWSTemplateFormatVersion: 2010-09-09
+
+Parameters:
+  EC2InstanceType:
+    Type: String
+  EC2AMI:
+    Type: String
+    Default: 'ami-09e67e426f25ce0d7'
+  DefaultAMI:
+    Type: String
+    Default: 'ami-09e67e426f25ce0d7'
+  KeyName:
+    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
+    Type: 'AWS::EC2::KeyPair::KeyName'
+    Default: 'joystream-key'
+    ConstraintDescription: must be the name of an existing EC2 KeyPair.
+
+Conditions:
+  HasAMIId: !Not [!Equals [!Ref EC2AMI, ""]]
+
+Resources:
+  SecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription:
+        !Sub 'Internal Security group for validator nodes ${AWS::StackName}'
+      SecurityGroupIngress:
+        - IpProtocol: tcp
+          FromPort: 30333
+          ToPort: 30333
+          CidrIp: 0.0.0.0/0
+        - IpProtocol: tcp
+          FromPort: 22
+          ToPort: 22
+          CidrIp: 0.0.0.0/0
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_validator'
+
+  RPCSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription:
+        !Sub 'Internal Security group for RPC nodes ${AWS::StackName}'
+      SecurityGroupIngress:
+        - IpProtocol: tcp
+          FromPort: 9933
+          ToPort: 9933
+          CidrIp: 0.0.0.0/0
+        - IpProtocol: tcp
+          FromPort: 9944
+          ToPort: 9944
+          CidrIp: 0.0.0.0/0
+        - IpProtocol: tcp
+          FromPort: 22
+          ToPort: 22
+          CidrIp: 0.0.0.0/0
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_rpc'
+
+  InstanceLaunchTemplate:
+    Type: AWS::EC2::LaunchTemplate
+    Metadata:
+      AWS::CloudFormation::Init:
+        config:
+          packages:
+            apt:
+              wget: []
+              unzip: []
+    Properties:
+      LaunchTemplateName: !Sub 'LaunchTemplate_${AWS::StackName}'
+      LaunchTemplateData:
+        ImageId: !If [HasAMIId, !Ref EC2AMI, !Ref DefaultAMI]
+        InstanceType: !Ref EC2InstanceType
+        KeyName: !Ref KeyName
+        SecurityGroupIds:
+          - !GetAtt SecurityGroup.GroupId
+        BlockDeviceMappings:
+          - DeviceName: /dev/sda1
+            Ebs:
+              VolumeSize: '40'
+        UserData:
+          Fn::Base64: !Sub |
+            #!/bin/bash -xe
+
+            # send script output to /tmp so we can debug boot failures
+            exec > /tmp/userdata.log 2>&1
+
+            # Update all packages
+            apt-get update -y
+
+            # Install the updates
+            apt-get upgrade -y
+
+            # Get latest cfn scripts and install them;
+            apt-get install -y python3-setuptools
+            mkdir -p /opt/aws/bin
+            wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
+            python3 -m easy_install --script-dir /opt/aws/bin aws-cfn-bootstrap-py3-latest.tar.gz
+
+            /opt/aws/bin/cfn-signal -e $? -r "Instance Created" '${WaitHandle}'
+
+  Instance:
+    Type: AWS::EC2::Instance
+    Properties:
+      LaunchTemplate:
+        LaunchTemplateId: !Ref InstanceLaunchTemplate
+        Version: !GetAtt InstanceLaunchTemplate.LatestVersionNumber
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_1'
+
+  Instance2:
+    Type: AWS::EC2::Instance
+    Properties:
+      LaunchTemplate:
+        LaunchTemplateId: !Ref InstanceLaunchTemplate
+        Version: !GetAtt InstanceLaunchTemplate.LatestVersionNumber
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_2'
+
+  RPCInstance:
+    Type: AWS::EC2::Instance
+    Properties:
+      SecurityGroupIds:
+        - !GetAtt RPCSecurityGroup.GroupId
+      LaunchTemplate:
+        LaunchTemplateId: !Ref InstanceLaunchTemplate
+        Version: !GetAtt InstanceLaunchTemplate.LatestVersionNumber
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_rpc'
+
+  BuildInstance:
+    Type: AWS::EC2::Instance
+    Properties:
+      LaunchTemplate:
+        LaunchTemplateId: !Ref InstanceLaunchTemplate
+        Version: !GetAtt InstanceLaunchTemplate.LatestVersionNumber
+      Tags:
+        - Key: Name
+          Value: !Sub '${AWS::StackName}_build'
+
+  WaitHandle:
+    Type: AWS::CloudFormation::WaitConditionHandle
+
+  WaitCondition:
+    Type: AWS::CloudFormation::WaitCondition
+    Properties:
+      Handle: !Ref 'WaitHandle'
+      Timeout: '600'
+      Count: 4
+
+Outputs:
+  PublicIp:
+    Description: The DNS name for the created instance
+    Value:  !Sub "${Instance.PublicIp}"
+    Export:
+      Name: !Sub "${AWS::StackName}PublicIp"
+
+  PublicIp2:
+    Description: The DNS name for the created instance
+    Value:  !Sub "${Instance2.PublicIp}"
+    Export:
+      Name: !Sub "${AWS::StackName}PublicIp2"
+
+  RPCPublicIp:
+    Description: The DNS name for the created instance
+    Value:  !Sub "${RPCInstance.PublicIp}"
+    Export:
+      Name: !Sub "${AWS::StackName}RPCPublicIp"
+
+  BuildPublicIp:
+    Description: The DNS name for the created instance
+    Value:  !Sub "${BuildInstance.PublicIp}"
+    Export:
+      Name: !Sub "${AWS::StackName}BuildPublicIp"

+ 18 - 0
devops/infrastructure/roles/admin/tasks/main.yml

@@ -0,0 +1,18 @@
+---
+# Configure admin server to be able to create chain-spec file and subkey commands
+
+- name: Copy bash_profile content
+  shell: cat ~/.bash_profile
+  register: bash_data
+
+- name: Copy bash_profile content to bashrc for non-interactive sessions
+  blockinfile:
+    block: "{{ bash_data.stdout }}"
+    path: ~/.bashrc
+    insertbefore: BOF
+
+- name: Get dependencies for subkey
+  shell: curl https://getsubstrate.io -sSf | bash -s -- --fast
+
+- name: Install subkey
+  shell: cargo install --force subkey --git https://github.com/paritytech/substrate --version 2.0.1 --locked

+ 76 - 0
devops/infrastructure/roles/common/tasks/chain-spec-node-keys.yml

@@ -0,0 +1,76 @@
+---
+# Create chain spec files and keys and copy to all the servers
+
+- name: Debug to test variable
+  debug:
+    msg: "Data path: {{ data_path }}, Chain Spec path: {{ chain_spec_path }}"
+  run_once: true
+
+- name: Run chain-spec-builder to generate chainspec.json file
+  command: "{{ admin_code_dir }}/target/release/chain-spec-builder generate -a 2 --chain-spec-path {{ chain_spec_path }} --deployment live --endowed 1 --keystore-path {{ data_path }}"
+  register: chain_spec_output
+  delegate_to: "{{ local_or_admin }}"
+  run_once: true
+
+- name: Run subkey to generate node keys
+  shell: subkey generate-node-key
+  delegate_to: "{{ local_or_admin }}"
+  register: subkey_output
+
+- name: Print to stdout
+  debug:
+    msg:
+    - "Public Key: {{ subkey_output.stderr }}"
+    - "Private Key: {{ subkey_output.stdout }}"
+
+- name: Print to stdout chain spec
+  debug: var=chain_spec_output.stdout
+  run_once: true
+
+- name: Save output of chain spec to local file
+  copy:
+    content: '{{ chain_spec_output.stdout | regex_replace("\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]", "") }}'
+    dest: "{{ data_path }}/chain_spec_output.txt"
+  delegate_to: "{{ local_or_admin }}"
+  run_once: true
+
+- name: Change chain spec name, id, protocolId
+  json_modify:
+    chain_spec_path: "{{ chain_spec_path }}"
+    prefix: "{{ network_suffix }}"
+    all_nodes: "{{ hostvars }}"
+  delegate_to: "{{ local_or_admin }}"
+  register: result
+  run_once: true
+
+- name: Print output of modified chainspec
+  debug:
+    var: result.result
+  run_once: true
+
+- name: Run build-spec to generate raw chainspec file
+  shell: "{{ admin_code_dir }}/target/release/joystream-node build-spec --chain {{ chain_spec_path }} --raw > {{ raw_chain_spec_path }}"
+  delegate_to: "{{ local_or_admin }}"
+  run_once: true
+
+- name: Copying chain spec files to localhost
+  synchronize:
+    src: "/home/ubuntu/{{ data_path }}/"
+    dest: "{{ data_path }}"
+    mode: pull
+  run_once: true
+  when: run_on_admin_server|bool
+
+- name: Copy joystream-node binary to localhost
+  fetch:
+    src: "{{ admin_code_dir }}/target/release/joystream-node"
+    dest: "{{ data_path }}/joystream-node"
+    flat: yes
+  delegate_to: "{{ local_or_admin }}"
+  run_once: true
+  when: run_on_admin_server|bool
+
+- name: Copying raw chain spec file to all servers
+  copy:
+    src: "{{ raw_chain_spec_path }}"
+    dest: "{{ remote_chain_spec_path }}"

+ 13 - 0
devops/infrastructure/roles/common/tasks/get-code-git.yml

@@ -0,0 +1,13 @@
+---
+# Get the latest code
+
+- name: Delete remote code directory if exists
+  file:
+    state: absent
+    path: "{{ remote_code_path }}"
+
+- name: Git checkout
+  git:
+    repo: "{{ git_repo }}"
+    dest: "{{ remote_code_path }}"
+    version: "{{ branch_name }}"

+ 27 - 0
devops/infrastructure/roles/common/tasks/get-code-local.yml

@@ -0,0 +1,27 @@
+---
+# Get the latest code
+
+- name: Archive the current Git repository
+  command: git archive --format tar HEAD
+  args:  
+    chdir: "{{ local_dir }}"
+  delegate_to: localhost
+  register: archive_output
+
+- name: Save output the git repo as an archive
+  local_action: copy content={{ archive_output.stdout }} dest="{{ local_dir }}/code-archive.tar"
+
+- name: Delete remote code directory if exists
+  file:
+    state: absent
+    path: "{{ remote_code_path }}"
+
+- name: create directory for unarchiving
+  file:
+    path: "{{ remote_code_path }}"
+    state: directory
+
+- name: Extract code into path
+  unarchive:
+    src: "{{ local_dir }}/code-archive.tar"
+    dest: "{{ remote_code_path }}"

+ 15 - 0
devops/infrastructure/roles/common/tasks/run-setup-build.yml

@@ -0,0 +1,15 @@
+---
+# Run setup and build code
+
+- name: Creat bash profile file
+  command: "touch /home/ubuntu/.bash_profile"
+
+- name: Run setup script
+  command: ./setup.sh
+  args:
+    chdir: "{{ remote_code_path }}"
+
+- name: Build joystream node
+  shell: . ~/.bash_profile && yarn cargo-build
+  args:
+    chdir: "{{ remote_code_path }}"

+ 22 - 0
devops/infrastructure/roles/rpc/tasks/main.yml

@@ -0,0 +1,22 @@
+---
+# Configure and start joystream-node RPC service on the servers
+
+- name: Print bootNodes
+  debug:
+    var: result.result.bootNodes
+  run_once: true
+
+- name: Create a service file
+  template:
+    src: joystream-node.service.j2
+    dest: /etc/systemd/system/joystream-node.service
+  vars:
+    template_remote_chain_spec_path: "{{ remote_chain_spec_path }}"
+    boot_nodes: "{{ result.result.bootNodes }}"
+  become: yes
+
+- name: Start service joystream-node, if not started
+  service:
+    name: joystream-node
+    state: started
+  become: yes

+ 25 - 0
devops/infrastructure/roles/rpc/templates/joystream-node.service.j2

@@ -0,0 +1,25 @@
+[Unit]
+Description=Joystream Node
+After=network.target
+
+[Service]
+Type=simple
+User=ubuntu
+WorkingDirectory=/home/ubuntu/joystream/
+ExecStart=/home/ubuntu/joystream/target/release/joystream-node \
+        --chain {{ template_remote_chain_spec_path }} \
+        --ws-external \
+        --rpc-cors all \
+        --pruning archive \
+        --ws-max-connections 512 \
+        --telemetry-url "wss://telemetry.joystream.org/submit/ 0" \
+        --telemetry-url "wss://telemetry.polkadot.io/submit/ 0"
+        --reserved-nodes \
+                {{ boot_nodes|join(" ") }}
+
+Restart=on-failure
+RestartSec=3
+LimitNOFILE=16384
+
+[Install]
+WantedBy=multi-user.target

+ 45 - 0
devops/infrastructure/roles/validators/tasks/main.yml

@@ -0,0 +1,45 @@
+---
+# Configure chain spec and start joystream-node service on the servers
+
+- set_fact:
+    chain_path: "{{ remote_code_path }}/chains/{{ result.result.id }}"
+
+- set_fact:
+    network_path: "{{ chain_path }}/network"
+    keystore_path: "{{ chain_path }}/keystore/"
+
+- set_fact:
+    secret_path: "{{ network_path }}/secret"
+
+- name: Creating chains directory
+  file:
+    path: "{{ item }}"
+    state: directory
+  loop:
+    - "{{ network_path }}"
+
+- name: Copy secret to remote host
+  copy:
+    dest: "{{ secret_path }}"
+    content: "{{ subkey_output.stdout }}"
+
+- name: Copy auth directory to remote host
+  copy:
+    src: "{{ data_path }}/auth-{{ ansible_play_batch.index(inventory_hostname) }}/"
+    dest: "{{ keystore_path }}"
+
+- name: Create a service file
+  template:
+    src: joystream-node.service.j2
+    dest: /etc/systemd/system/joystream-node.service
+  vars:
+    template_keystore_path: "{{ keystore_path }}"
+    template_secret_path: "{{ secret_path }}"
+    template_remote_chain_spec_path: "{{ remote_chain_spec_path }}"
+  become: yes
+
+- name: Start service joystream-node, if not started
+  service:
+    name: joystream-node
+    state: started
+  become: yes

+ 21 - 0
devops/infrastructure/roles/validators/templates/joystream-node.service.j2

@@ -0,0 +1,21 @@
+[Unit]
+Description=Joystream Node
+After=network.target
+
+[Service]
+Type=simple
+User=ubuntu
+WorkingDirectory=/home/ubuntu/joystream/
+ExecStart=/home/ubuntu/joystream/target/release/joystream-node \
+        --chain {{ template_remote_chain_spec_path }} \
+        --pruning archive \
+        --node-key-file {{ template_secret_path }} \
+        --keystore-path {{ template_keystore_path }} \
+        --validator \
+        --log runtime,txpool,transaction-pool,trace=sync
+Restart=on-failure
+RestartSec=3
+LimitNOFILE=10000
+
+[Install]
+WantedBy=multi-user.target

+ 9 - 0
devops/infrastructure/setup-admin.yml

@@ -0,0 +1,9 @@
+---
+# Setup Build server install subkey
+
+- name: Setup build server, install subkey
+  hosts: build
+
+  roles:
+    - role: admin
+      when: run_on_admin_server|bool