From 2bed864f91823c33b64086308bd1240508dee05d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 14 Dec 2017 19:38:50 +0100 Subject: [PATCH] Initial commit --- .gitignore | 17 ++ LICENSE | 20 ++ README.md | 41 +++ ansible.cfg | 5 + cluster.yml | 84 ++++++ clusters/README.md | 1 + clusters/aws-example.tfvars | 9 + clusters/aws-example.yml | 66 +++++ group_vars/all | 66 +++++ kill-cluster.yml | 5 + roles/aws/tasks/main.yml | 14 + roles/cpuminer/defaults/main.yml | 3 + roles/cpuminer/tasks/main.yml | 11 + roles/dash-cli/tasks/main.yml | 7 + roles/dashd/defaults/main.yml | 16 ++ roles/dashd/tasks/main.yml | 60 ++++ roles/dashd/templates/dash.conf.j2 | 67 +++++ roles/ecr-login/tasks/main.yml | 19 ++ roles/generate-blocks/tasks/main.yml | 12 + .../tasks/regtest_generate.yml | 18 ++ .../generate-blocks/tasks/wait_for_blocks.yml | 33 +++ roles/generate-firstblock/tasks/main.yml | 18 ++ roles/kill-cluster/tasks/main.yml | 9 + roles/mn-conf/tasks/main.yml | 42 +++ roles/mn-conf/templates/masternode.conf.j2 | 7 + .../scripts/find-collateral.py | 68 +++++ roles/mn-find-collateral/tasks/main.yml | 41 +++ .../tasks/fund_collateral.yml | 12 + roles/mn-fund-collateral/tasks/main.yml | 71 +++++ roles/mn-pay-faucet/tasks/main.yml | 27 ++ .../templates/pay-faucet.service.j2 | 8 + .../mn-pay-faucet/templates/pay-faucet.sh.j2 | 18 ++ roles/mn-sentinel/tasks/main.yml | 20 ++ .../mn-sentinel/templates/docker-compose.yml | 13 + roles/mn-start/defaults/main.yml | 3 + roles/mn-start/tasks/main.yml | 23 ++ roles/multifaucet/defaults/main.yml | 3 + roles/multifaucet/tasks/main.yml | 28 ++ .../multifaucet/templates/docker-compose.yml | 28 ++ roles/multifaucet/templates/init.sql | 14 + .../templates/multifaucet/Dockerfile | 38 +++ .../templates/multifaucet/db.conf.php | 7 + .../templates/multifaucet/faucet.conf.php | 49 ++++ .../multifaucet/templates/multifaucet/php.ini | 4 + .../templates/multifaucet/wallet.conf.php | 11 + roles/regtest-miner/tasks/main.yml | 27 ++ .../templates/regtest-miner.service.j2 | 10 + .../templates/regtest-miner.sh.j2 | 20 ++ terraform/aws/inventory/ansible_inventory.tpl | 25 ++ terraform/aws/inventory/hostname.tpl | 1 + terraform/aws/main.tf | 268 ++++++++++++++++++ terraform/aws/outputs.tf | 73 +++++ terraform/aws/variables.tf | 50 ++++ 53 files changed, 1610 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ansible.cfg create mode 100644 cluster.yml create mode 100644 clusters/README.md create mode 100644 clusters/aws-example.tfvars create mode 100644 clusters/aws-example.yml create mode 100644 group_vars/all create mode 100644 kill-cluster.yml create mode 100644 roles/aws/tasks/main.yml create mode 100644 roles/cpuminer/defaults/main.yml create mode 100644 roles/cpuminer/tasks/main.yml create mode 100644 roles/dash-cli/tasks/main.yml create mode 100644 roles/dashd/defaults/main.yml create mode 100644 roles/dashd/tasks/main.yml create mode 100644 roles/dashd/templates/dash.conf.j2 create mode 100644 roles/ecr-login/tasks/main.yml create mode 100644 roles/generate-blocks/tasks/main.yml create mode 100644 roles/generate-blocks/tasks/regtest_generate.yml create mode 100644 roles/generate-blocks/tasks/wait_for_blocks.yml create mode 100644 roles/generate-firstblock/tasks/main.yml create mode 100644 roles/kill-cluster/tasks/main.yml create mode 100644 roles/mn-conf/tasks/main.yml create mode 100644 roles/mn-conf/templates/masternode.conf.j2 create mode 100644 roles/mn-find-collateral/scripts/find-collateral.py create mode 100644 roles/mn-find-collateral/tasks/main.yml create mode 100644 roles/mn-fund-collateral/tasks/fund_collateral.yml create mode 100644 roles/mn-fund-collateral/tasks/main.yml create mode 100644 roles/mn-pay-faucet/tasks/main.yml create mode 100644 roles/mn-pay-faucet/templates/pay-faucet.service.j2 create mode 100644 roles/mn-pay-faucet/templates/pay-faucet.sh.j2 create mode 100644 roles/mn-sentinel/tasks/main.yml create mode 100644 roles/mn-sentinel/templates/docker-compose.yml create mode 100644 roles/mn-start/defaults/main.yml create mode 100644 roles/mn-start/tasks/main.yml create mode 100644 roles/multifaucet/defaults/main.yml create mode 100644 roles/multifaucet/tasks/main.yml create mode 100644 roles/multifaucet/templates/docker-compose.yml create mode 100644 roles/multifaucet/templates/init.sql create mode 100644 roles/multifaucet/templates/multifaucet/Dockerfile create mode 100644 roles/multifaucet/templates/multifaucet/db.conf.php create mode 100644 roles/multifaucet/templates/multifaucet/faucet.conf.php create mode 100644 roles/multifaucet/templates/multifaucet/php.ini create mode 100644 roles/multifaucet/templates/multifaucet/wallet.conf.php create mode 100644 roles/regtest-miner/tasks/main.yml create mode 100644 roles/regtest-miner/templates/regtest-miner.service.j2 create mode 100644 roles/regtest-miner/templates/regtest-miner.sh.j2 create mode 100644 terraform/aws/inventory/ansible_inventory.tpl create mode 100644 terraform/aws/inventory/hostname.tpl create mode 100644 terraform/aws/main.tf create mode 100644 terraform/aws/outputs.tf create mode 100644 terraform/aws/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7c403104 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.env + +# Ansible +*.retry + +# Vagrant +.vagrant + +# Terraform +*.tfstate +*.tfstate.backup +.terraform +/clusters/aws-example.inventory + +# CLion +cmake-build-debug +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5d0bd389 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 The Dash Core Group, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..1b0853b8 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +Dash Cluster Ansible +==================== + +What is this? +------------- + +This is an Ansible project to setup and manage devnet (and later testnet) networks. Devnets +are like regular Dash networks (mainnet, testnet) but easier to bootstrap and easier to have +multiple in parallel. + +This project is work in progress and in its initial state only meant to be used by Dash Core +developers to assist in Dash Evolution development. + +Detailed documentation on how to use this repo will come when more interest from the public +arises or when Evolution is released. + +Getting started +--------------- + +Create your own cluster configuration in `clusters`. Use `aws-example.*` as a skeleton. The +following commands will all directly use the aws-example, while you should change it to use +your own config. + +Setup the AWS infrastructure with terraform: + +```bash +$ cd terraform/aws +$ terraform apply -var-file=../../clusters/aws-example.tfvars +``` + +Create the inventory file for Ansible + +```bash +$ terraform output ansible_inventory > ../../clusters/aws-example.inventory +``` + +Invoke ansible-playbook +```bash +$ cd ../.. # Go back to root dir of project +$ ansible-playbook -i clusters/aws-example.inventory -e @clusters/aws-example.yml cluster.yml +``` diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 00000000..2a64d359 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +host_key_checking = False + +[ssh_connection] +pipelining = True \ No newline at end of file diff --git a/cluster.yml b/cluster.yml new file mode 100644 index 00000000..d680e363 --- /dev/null +++ b/cluster.yml @@ -0,0 +1,84 @@ +--- +###################### +# Bootstrap + +- hosts: all + gather_facts: False + become: true + pre_tasks: + - name: install python 2 + raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal) + - name: Check if inside AWS. + uri: + url: http://169.254.169.254/latest/meta-data + timeout: 2 + register: aws_uri_check + failed_when: False + - set_fact: + is_aws_environment: "{{ aws_uri_check.status == 200 }}" + roles: + - role: aws + when: is_aws_environment + +###################### + +- hosts: all + become: true + vars: + pip_install_packages: + - name: docker + - name: docker-compose + roles: + - geerlingguy.pip + - geerlingguy.docker + - ecr-login + - dash-cli + +# private nodes +- hosts: wallet-nodes + become: true + roles: + - role: dashd + dashd_indexes: true + dashd_zmq: true + +# public nodes +- hosts: full-nodes, masternodes + become: true + pre_tasks: + - set_fact: + masternode: '{{ masternodes[inventory_hostname] }}' + when: inventory_hostname in masternodes + roles: + - role: dashd + dashd_listen: true + - role: mn-sentinel + when: masternode is defined + +# generate first block on seed node so that all nodes leave IBD mode (required for mining) +- hosts: seed-node + become: true + roles: + - role: generate-firstblock + when: dash_network == "devnet" or dash_network == "regtest" + +- hosts: miners + become: true + roles: + - role: regtest-miner + when: dash_network == "regtest" + - role: cpuminer + when: dash_network != "regtest" + +- hosts: web + become: true + roles: + - multifaucet + +- hosts: masternode-wallet + become: true + roles: + - mn-fund-collateral + - mn-pay-faucet + - mn-conf + - mn-start diff --git a/clusters/README.md b/clusters/README.md new file mode 100644 index 00000000..c70a97bd --- /dev/null +++ b/clusters/README.md @@ -0,0 +1 @@ +Put your cluster configurations here. See aws-example.yml for an example \ No newline at end of file diff --git a/clusters/aws-example.tfvars b/clusters/aws-example.tfvars new file mode 100644 index 00000000..0d54a3c0 --- /dev/null +++ b/clusters/aws-example.tfvars @@ -0,0 +1,9 @@ +dash_network="devnet" +dash_devnet_name="aws-example" +dashd_port=20001 +dashd_rpc_port=20002 + +node_count=1 +miner_count=2 +masternode_count = 13 + diff --git a/clusters/aws-example.yml b/clusters/aws-example.yml new file mode 100644 index 00000000..6fa71496 --- /dev/null +++ b/clusters/aws-example.yml @@ -0,0 +1,66 @@ +--- + +dash_network: devnet +dash_devnet_name: aws-example +dashd_port: 20001 +dashd_rpcport: 20002 + +dashd_rpcuser: dashrpc +dashd_rpcpassword: password + +faucet_address: yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5 +faucet_privkey: cR4t6evwVZoCp1JsLk4wURK4UmBCZzZotNzn9T1mhBT19SH9JtNt + +masternodes: + masternode-1: + address: yiRNTUNCS39WPu3VhT7utRWTJYzmUdf1aR + address_key: cSrCXqZ1oVw3vpeuoyPKHBpGiMmf6pQerF64MYMcS11HQ4bGquDF + privkey: 92KdqxzX7HCnxCtwt1yHENGrXq71SAxD4vrrsFArbSU2wUKdQCM + masternode-2: + address: ybpwqoZ2Q4goxrzgPx9b3cbPrvy6UbC1ct + address_key: cN88MoW1XEv1s4wt6zgbQ8aR9KYM4RZihSeoRUcdWmwdAyu6HaDu + privkey: 93Fd7XY2zF4q9YKTZUSFxLgp4Xs7MuaMnvY9kpvH7V8oXWqsCC1 + masternode-3: + address: yQuwR1uaci2SCQBgqcaKRzfq7upL4n513j + address_key: cTEGsaAqx9jUFYkNECeGKfixa6vgMiUoSwqsdncc11mS6BZerTpT + privkey: 93EmoMmHgTCg8yVhRtK5sxpv5k4Sq1KoHCVB43VxbbZpVPtHMnc + masternode-4: + address: yQCV7FnK99crDwmiwrLCwgaDKz5qV4my6u + address_key: cSMvsssFKziHctH2d2FNfeCaKuEckC9PB8aQD1E6ecYjSpDdYrLf + privkey: 931yVsW5MFNCRSPDYPR6UFJHp3SVRDkuhGGwqgmPiEaayRrdcoW + masternode-5: + address: yiS4S2aLvXf61WcJEc2mzkuLHCeBgd7wLp + address_key: cVMFBkeZ4X3HAz14CjQnpA9KqDSLGUbxNVx5BxXMxkBj4Us3HDpc + privkey: 92Srupy73xqSbXVK2MARo8Euq9kmdv5KVsaeZtdNCxQREU9xS4x + masternode-6: + address: yZrCcddoxdH16STwQpmpEPbn6urenS8yjf + address_key: cRNS6N2gakrSZWEz4NsxLwSzv9GYxS9wS4efLyntKo66ZiaenZyo + privkey: 92D4QRUTz1svVMgfmXrHqpYV7JZQ1ECP1d65m6j9AuLY9Wr6mGX + masternode-7: + address: yQSQoco2uAR4sc2AkXGNcAcBnbYY4q7a6T + address_key: cNj3itrDW4abS97dv6fE1dy16kDbfB8K63zws8Q65xMTEgZ8Gtqz + privkey: 92mSVHx5eji6QHAiMANYKMFcVeoukwf7cGG9SjvR3jbG9n5nFzu + masternode-8: + address: yXrf4MNYipnGmSQqGv1HGqmD3xxWELs9T9 + address_key: cQy6bcyDYwHR97wa4zUnuJAp6Fw3gJHoq1nFBCXqJBs2G17siFki + privkey: 92iu2nJLxreHMVSzMn7XeAS3zScuNPMocp9wrLUcCHJdgzLRFzR + masternode-9: + address: yTezNspASgUWeRvkVbQx7brFzJYnihkCdB + address_key: cQKSAVBd87j5EGtWEf2m5zip1qFmWBWzZ3yRhGeTMvB8wjLarxM2 + privkey: 92C4n8apRbzU91ETwM1KxAMGzzsjy9mRtqph9NptbRc5XJV1xAg + masternode-10: + address: yN4YJuZUUCiVTNoKwtNsNTapGvW9PbmzXk + address_key: cMdZCshfc8mhSTb8oauuraWKAZjfGDnx4xX9HE2D4nP74WwEk1HN + privkey: 91f9gi61MzGFSBuwzq5zZq4NHnA8vGL3qUUZyNijGWnimEtfnuq + masternode-11: + address: yTWgsUVk66vV2csfYFDxvYU31BupXDD2mE + address_key: cPaGBsFGjvsxBqZKzSCdERACg7EPkKBJf8kP4h6f9S8ZZeCoBqHh + privkey: 93Mhiq2KS5ZUGo4M7XnvuccMZQSvLvt81Ac5A7LgrH17DcE9Cp2 + masternode-12: + address: yUtqnQw3DHgn6BEwjnR1hVabKDM4F8pAuP + address_key: cNsvfpZb3RY3k7LvcxGriGavBqznE4Cn1q1QF8bGZo5eHMu7ngW3 + privkey: 91k7JxVWDDBoWpA8DZHc9FZwV5b1wZEf52v1m5dwzrzroyxhxHN + masternode-13: + address: yYzxjiTcG8HuKBXS3VW5yhjfZdMhMV1NqB + address_key: cPknbev5GEhHKug7QNzPRb4jpDM7PaJtfyaZgtivV86NBeCVvPFf + privkey: 933cojtvorhVLJxDdhaM9hf1wp7es7VHFxbms26DQ2tnZjxdgku diff --git a/group_vars/all b/group_vars/all new file mode 100644 index 00000000..ab157cb3 --- /dev/null +++ b/group_vars/all @@ -0,0 +1,66 @@ +--- + +dash_docker_repo: dashpay +dashd_image: '{{ dash_docker_repo }}/dashd-develop' + +dashd_user: dash +dashd_group: dash +dashd_home: /dash + +dash_network: devnet +dash_devnet_name: devnet1 +dashd_port: 29999 + +# Please provide your own credentials in cluster config +dashd_rpcuser: dashrpc +dashd_rpcpassword: password +dashd_rpcport: 29998 + +# You can provide your own seed node in cluster config. May be useful if you want to connect to another cluster +dashd_seednode: '{{ hostvars[groups["seed-node"][0]]["public_ip"] }}' + +# Faucet stuff + +faucet_rpc_host: '{{ hostvars[groups["faucet-wallet"][0]]["private_ip"] }}' +faucet_rpc_port: '{{ dashd_rpcport }}' +faucet_rpc_user: '{{ dashd_rpcuser }}' +faucet_rpc_password: '{{ dashd_rpcpassword }}' +faucet_rpc_args: '-rpcconnect={{ faucet_rpc_host }} -rpcport={{ faucet_rpc_port }} -rpcuser={{ faucet_rpc_user }} -rpcpassword={{ faucet_rpc_password }}' + +# Example faucet address/privkey (provide your own in cluster config) +#faucet_address: yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5 +#faucet_privkey: cR4t6evwVZoCp1JsLk4wURK4UmBCZzZotNzn9T1mhBT19SH9JtNt + +# Miner stuff + +miner_rpc_host: '{{ faucet_rpc_host }}' +miner_rpc_port: '{{ faucet_rpc_port }}' +miner_rpc_user: '{{ faucet_rpc_user }}' +miner_rpc_password: '{{ faucet_rpc_password }}' +miner_payment_address: "{{ faucet_address }}" + +# Allow to run miners at 100% by default +miner_cpu_quota: 100000 # 100% + +# Masternode stuff + +masternode_wallet_rpc_host: '{{ hostvars[groups["masternode-wallet"][0]]["private_ip"] }}' +masternode_wallet_rpc_port: '{{ dashd_rpcport }}' +masternode_wallet_rpc_user: '{{ dashd_rpcuser }}' +masternode_wallet_rpc_password: '{{ dashd_rpcpassword }}' +masternode_wallet_rpc_args: '-rpcconnect={{ masternode_wallet_rpc_host }} -rpcport={{ masternode_wallet_rpc_port }} -rpcuser={{ masternode_wallet_rpc_user }} -rpcpassword={{ masternode_wallet_rpc_password }}' + +# Example masternodes list (provide your own in cluster config) +#masternodes: +# masternode-1: +# address: yiRNTUNCS39WPu3VhT7utRWTJYzmUdf1aR +# address_key: cSrCXqZ1oVw3vpeuoyPKHBpGiMmf6pQerF64MYMcS11HQ4bGquDF +# privkey: 92KdqxzX7HCnxCtwt1yHENGrXq71SAxD4vrrsFArbSU2wUKdQCM +# masternode-2: +# address: ybpwqoZ2Q4goxrzgPx9b3cbPrvy6UbC1ct +# address_key: cN88MoW1XEv1s4wt6zgbQ8aR9KYM4RZihSeoRUcdWmwdAyu6HaDu +# privkey: 93Fd7XY2zF4q9YKTZUSFxLgp4Xs7MuaMnvY9kpvH7V8oXWqsCC1 +# masternode-3: +# address: yQuwR1uaci2SCQBgqcaKRzfq7upL4n513j +# address_key: cTEGsaAqx9jUFYkNECeGKfixa6vgMiUoSwqsdncc11mS6BZerTpT +# privkey: 93EmoMmHgTCg8yVhRtK5sxpv5k4Sq1KoHCVB43VxbbZpVPtHMnc diff --git a/kill-cluster.yml b/kill-cluster.yml new file mode 100644 index 00000000..667f658b --- /dev/null +++ b/kill-cluster.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + become: true + roles: + - kill-cluster diff --git a/roles/aws/tasks/main.yml b/roles/aws/tasks/main.yml new file mode 100644 index 00000000..981e2eb3 --- /dev/null +++ b/roles/aws/tasks/main.yml @@ -0,0 +1,14 @@ +--- + +- name: update hostname + hostname: + name: '{{ inventory_hostname }}' + +- name: ensure hostname is in /etc/hosts + lineinfile: + path: /etc/hosts + regexp: '^127\.0\.0\.1' + line: '127.0.0.1 localhost {{ inventory_hostname }}' + owner: root + group: root + mode: 0644 \ No newline at end of file diff --git a/roles/cpuminer/defaults/main.yml b/roles/cpuminer/defaults/main.yml new file mode 100644 index 00000000..3d5f361f --- /dev/null +++ b/roles/cpuminer/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +cpuminer_image: 'ablock/dash-cpuminer' diff --git a/roles/cpuminer/tasks/main.yml b/roles/cpuminer/tasks/main.yml new file mode 100644 index 00000000..0e6a2c25 --- /dev/null +++ b/roles/cpuminer/tasks/main.yml @@ -0,0 +1,11 @@ +--- + +- name: create cpuminer container + docker_container: + name: dash-cpuminer + state: started + #cpu_quota: "{{ miner_cpu_quota }}" + image: '{{ cpuminer_image }}' + pull: true + restart_policy: always + command: '-r 1 -a x11 --no-getwork --no-stratum -o http://{{ miner_rpc_host }}:{{ miner_rpc_port }} -u {{ miner_rpc_user }} -p {{ miner_rpc_password }} --coinbase-addr={{ miner_payment_address }}' diff --git a/roles/dash-cli/tasks/main.yml b/roles/dash-cli/tasks/main.yml new file mode 100644 index 00000000..a7448628 --- /dev/null +++ b/roles/dash-cli/tasks/main.yml @@ -0,0 +1,7 @@ +--- + +- name: pull dashd image + shell: docker pull {{ dashd_image }} + +- name: copy dash-cli from docker container + shell: docker run --rm -v /tmp:/host-tmp {{ dashd_image }} cp /usr/bin/dash-cli /host-tmp/ && mv /tmp/dash-cli /usr/local/bin/dash-cli diff --git a/roles/dashd/defaults/main.yml b/roles/dashd/defaults/main.yml new file mode 100644 index 00000000..2bf00e9a --- /dev/null +++ b/roles/dashd/defaults/main.yml @@ -0,0 +1,16 @@ +--- + +dashd_debug: 1 + +dashd_indexes: false +dashd_zmq: false + +dashd_listen: false +dashd_externalip: '{{ public_ip }}' +dashd_isseednode: '{{ dashd_externalip == dashd_seednode }}' + +dashd_private_net_prefix: 16 +dashd_private_cidr: '{{ private_ip|ipsubnet(dashd_private_net_prefix) }}' + +# When running devnet/regtest in local networks, we have to allow RFC1918/private addresses +dashd_allowprivatenet: '{% if dashd_externalip|ipaddr("private") == dashd_externalip %}1{% else %}0{% endif %}' diff --git a/roles/dashd/tasks/main.yml b/roles/dashd/tasks/main.yml new file mode 100644 index 00000000..7e1b595e --- /dev/null +++ b/roles/dashd/tasks/main.yml @@ -0,0 +1,60 @@ +--- + +- name: Create dashd group + group: + name: '{{ dashd_group }}' + +- name: Create dashd user + user: + name: '{{ dashd_user }}' + group: '{{ dashd_group }}' + append: False + home: '{{ dashd_home }}' + +- name: get uid of dash user + shell: id -u {{ dashd_user }} + register: dash_user_id +- name: get gid of dash user + shell: id -g {{ dashd_user }} + register: dash_group_id + +- name: create dash home/data dir + file: path={{ dashd_home }} state=directory mode='0750' owner='{{ dashd_user }}' group='{{ dashd_group }}' +- name: create .dashcore dir + file: path={{ dashd_home }}/.dashcore state=directory mode='0750' owner='{{ dashd_user }}' group='{{ dashd_group }}' +- name: create .dashcore dir for root + file: path=/root/.dashcore state=directory mode='0750' owner='root' group='root' + +- name: Configure dashd + template: + src: 'dash.conf.j2' + dest: '{{ dashd_home }}/.dashcore/dash.conf' + owner: '{{ dashd_user }}' + group: '{{ dashd_group }}' + mode: '0640' + register: dash_config_state + +- name: copy configuration to root user dir to make rpc work + shell: cp {{ dashd_home }}/.dashcore/dash.conf /root/.dashcore/ + +- name: create dashd container + docker_container: + name: dashd + state: started + restart: '{{ dash_config_state|changed }}' + restart_policy: always + image: '{{ dashd_image }}' + pull: true + user: '{{ dash_user_id.stdout }}:{{ dash_group_id.stdout }}' + working_dir: '{{ dashd_home }}' + volumes: + - /dash:/dash + network_mode: host + command: 'dashd -conf={{ dashd_home }}/.dashcore/dash.conf -datadir={{ dashd_home }}/.dashcore' + +- name: wait for rpc to be available + shell: dash-cli getinfo + register: task_result + until: task_result.rc == 0 + retries: 5 + delay: 10 diff --git a/roles/dashd/templates/dash.conf.j2 b/roles/dashd/templates/dash.conf.j2 new file mode 100644 index 00000000..37cd9802 --- /dev/null +++ b/roles/dashd/templates/dash.conf.j2 @@ -0,0 +1,67 @@ +# general + +{% if dash_network == 'regtest' %} +regtest=1 +{% elif dash_network == 'testnet' %} +testnet=1 +{% elif dash_network == 'devnet' %} +devnet={{ dash_devnet_name }} +{% endif %} + +daemon=0 # leave this set to 0 for Docker +logtimestamps=1 +maxconnections=256 +debug={{ dashd_debug }} + +{% if dashd_indexes %} +# optional indices (required for Insight) +txindex=1 +addressindex=1 +timestampindex=1 +spentindex=1 +{% endif %} + +{% if dashd_zmq %} +# ZeroMQ notifications (required for Insight) +zmqpubrawtx=tcp://0.0.0.0:29998 +zmqpubrawtxlock=tcp://0.0.0.0:29998 +zmqpubhashblock=tcp://0.0.0.0:29998 +#zmqpubhashtx=tcp://0.0.0.0:29998 +#zmqpubhashtxlock=tcp://0.0.0.0:29998 +#zmqpubrawblock=tcp://0.0.0.0:29998 +{% endif %} + +# JSONRPC +server=1 +rpcport={{ dashd_rpcport }} +rpcuser={{ dashd_rpcuser }} +rpcpassword={{ dashd_rpcpassword }} +rpcallowip={{ dashd_private_cidr }} +rpcallowip=127.0.0.0 +# docker-to-docker +rpcallowip=172.18.0.0/16 +rpcworkqueue=64 +rpcthreads=16 + +# external network +{% if dashd_listen %} +listen=1 +{% else %} +listen=0 +{% endif %} +externalip={{ dashd_externalip }} +bind=0.0.0.0 +port={{ dashd_port }} +dnsseed=0 +allowprivatenet={{ dashd_allowprivatenet }} + +printtoconsole=1 + +{% if dashd_seednode is not none and not dashd_isseednode %} +addnode={{ dashd_seednode }}:{{ dashd_port }} +{% endif %} + +{% if masternode is defined %} +masternode=1 +masternodeprivkey={{ masternode.privkey }} +{% endif %} diff --git a/roles/ecr-login/tasks/main.yml b/roles/ecr-login/tasks/main.yml new file mode 100644 index 00000000..05778081 --- /dev/null +++ b/roles/ecr-login/tasks/main.yml @@ -0,0 +1,19 @@ +--- + +# todo more intelligent need_login (only every few hours) +- set_fact: + need_login: true + +- set_fact: + has_aws_creds: '{{ lookup("env", "AWS_ACCESS_KEY_ID") != "" }}' + delegate_to: localhost + +- name: get ecr login command + become: false + local_action: command aws ecr get-login --no-include-email + register: ecr_login_command + when: has_aws_creds and need_login + +- name: docker login + shell: "{{ ecr_login_command.stdout }}" + when: has_aws_creds and need_login diff --git a/roles/generate-blocks/tasks/main.yml b/roles/generate-blocks/tasks/main.yml new file mode 100644 index 00000000..87b2b313 --- /dev/null +++ b/roles/generate-blocks/tasks/main.yml @@ -0,0 +1,12 @@ +--- + +- fail: msg="num_blocks and balance_needed must always be set. set one of them to a non-zero value and the other one to zero" + when: num_blocks is not defined or balance_needed is not defined or (num_blocks != 0 and balance_needed != 0) or (num_blocks == 0 and balance_needed == 0) + +- debug: msg="num_blocks={{ num_blocks }} balance_needed={{ balance_needed }}" + +- include: regtest_generate.yml + when: dash_network == "regtest" + +- include: wait_for_blocks.yml + when: dash_network != "regtest" diff --git a/roles/generate-blocks/tasks/regtest_generate.yml b/roles/generate-blocks/tasks/regtest_generate.yml new file mode 100644 index 00000000..9f30743f --- /dev/null +++ b/roles/generate-blocks/tasks/regtest_generate.yml @@ -0,0 +1,18 @@ +--- + +- name: generate fixed amount of blocks + shell: dash-cli {{ faucet_rpc_args }} generate {{ num_blocks }} > /dev/null + when: num_blocks > 0 + +# TODO avoid first call if not needed +- name: generate enough blocks to have {{ balance_needed|default(0) }} coins + shell: dash-cli {{ faucet_rpc_args }} generate 10 > /dev/null && dash-cli {{ faucet_rpc_args }} getbalance + when: balance_needed|float > 0 + register: balance_result + until: balance_result.stdout|float >= balance_needed|float + retries: 200 + delay: 0 + +- set_fact: + new_balance: "{{ balance_result.stdout|float }}" + when: balance_needed|float > 0 diff --git a/roles/generate-blocks/tasks/wait_for_blocks.yml b/roles/generate-blocks/tasks/wait_for_blocks.yml new file mode 100644 index 00000000..da0a3734 --- /dev/null +++ b/roles/generate-blocks/tasks/wait_for_blocks.yml @@ -0,0 +1,33 @@ +--- + +- name: getblockchaininfo + command: dash-cli {{ faucet_rpc_args }} getblockchaininfo + when: num_blocks > 0 + register: getblockchaininfo_result + +- set_fact: + initial_block_count: "{{ (getblockchaininfo_result.stdout|from_json).blocks|int }}" + when: num_blocks > 0 + +- debug: var=initial_block_count + when: num_blocks > 0 + +- name: wait for {{ num_blocks }} blocks + command: dash-cli {{ faucet_rpc_args }} getblockchaininfo + when: num_blocks > 0 + register: getblockchaininfo_result + until: (getblockchaininfo_result.stdout|from_json).blocks|int >= (initial_block_count|int + num_blocks|int) + retries: 200 + delay: 10 + +- name: wait for enough blocks to have {{ balance_needed|default(0) }} coins + command: dash-cli {{ faucet_rpc_args }} getbalance + when: balance_needed|float > 0 + register: balance_result + until: balance_result.stdout|float >= balance_needed|float + retries: 200 + delay: 10 + +- set_fact: + new_balance: "{{ balance_result.stdout|float }}" + when: balance_needed|float > 0 diff --git a/roles/generate-firstblock/tasks/main.yml b/roles/generate-firstblock/tasks/main.yml new file mode 100644 index 00000000..dc576df5 --- /dev/null +++ b/roles/generate-firstblock/tasks/main.yml @@ -0,0 +1,18 @@ +--- + +- name: getblockchaininfo + command: dash-cli getblockchaininfo + register: getblockchaininfo_result + +- set_fact: + initial_block_count: "{{ (getblockchaininfo_result.stdout|from_json).blocks }}" + required_block_count: 1 +- set_fact: + required_block_count: 2 + when: dash_network == "devnet" # devnet has 2 genesis blocks + +- debug: var=initial_block_count + +- name: generate first block + command: dash-cli generate 1 + when: initial_block_count|int < required_block_count|int diff --git a/roles/kill-cluster/tasks/main.yml b/roles/kill-cluster/tasks/main.yml new file mode 100644 index 00000000..6702e4de --- /dev/null +++ b/roles/kill-cluster/tasks/main.yml @@ -0,0 +1,9 @@ +--- + +- name: kill all docker containers + shell: if [ -n "$(docker ps -qa)" ]; then docker rm -fv $(docker ps -qa); fi + +- name: delete dash home dir + file: + state: absent + path: "{{ dashd_home }}/" \ No newline at end of file diff --git a/roles/mn-conf/tasks/main.yml b/roles/mn-conf/tasks/main.yml new file mode 100644 index 00000000..06a12228 --- /dev/null +++ b/roles/mn-conf/tasks/main.yml @@ -0,0 +1,42 @@ +--- + +- set_fact: + collateral_ok_count: 0 + +- include_role: + name: mn-find-collateral + vars: + masternode: '{{ masternodes[item] }}' + masternode_name: '{{ item }}' + with_items: '{{ groups["masternodes"] }}' + +- name: fail if not all collaterals are ok + fail: + msg: "could not find collateral" + when: collateral_ok_count|int != groups["masternodes"]|length + +- set_fact: + net_subdir: "" +- name: set net_subdir for non-mainnet nodes + set_fact: + net_subdir: "/{{ dash_network }}" + when: dash_network != "mainnet" +- name: append devnet name to net_subdir + set_fact: + net_subdir: "{{ net_subdir }}-{{ dash_devnet_name }}" + when: dash_network == "devnet" + +- debug: var=net_subdir + +- name: create masternode.conf + template: + src: 'masternode.conf.j2' + dest: '{{ dashd_home }}/.dashcore{{ net_subdir }}/masternode.conf' + owner: '{{ dashd_user }}' + group: '{{ dashd_group }}' + mode: '0640' + register: masternode_config_state + +- name: restart dashd + command: docker restart dashd + when: masternode_config_state.changed \ No newline at end of file diff --git a/roles/mn-conf/templates/masternode.conf.j2 b/roles/mn-conf/templates/masternode.conf.j2 new file mode 100644 index 00000000..12faa615 --- /dev/null +++ b/roles/mn-conf/templates/masternode.conf.j2 @@ -0,0 +1,7 @@ +{% for name, masternode in masternodes.items() %} +{% if name in groups['masternodes'] %} +{% set txid_var_name = "collateral_txid_" + name|replace("-","_") %} +{% set vout_var_name = "collateral_vout_" + name|replace("-","_") %} +{{ name }} {{ hostvars[name].public_ip }}:{{ dashd_port }} {{ masternode.privkey }} {{ vars[txid_var_name] }} {{ vars[vout_var_name] }} +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/roles/mn-find-collateral/scripts/find-collateral.py b/roles/mn-find-collateral/scripts/find-collateral.py new file mode 100644 index 00000000..1e92f45b --- /dev/null +++ b/roles/mn-find-collateral/scripts/find-collateral.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +import json + +COIN = 100000000 + +rpcargs = sys.argv[1] +mn_address = sys.argv[2] + +blockchaininfo_s = subprocess.run("dash-cli %s getblockchaininfo" % (rpcargs), shell=True, check=True, stdout=subprocess.PIPE).stdout.decode("utf-8") +unspent_s = subprocess.run("dash-cli %s listunspent 0 9999999 \'[\"%s\"]\'" % (rpcargs, mn_address), shell=True, check=True, stdout=subprocess.PIPE).stdout.decode("utf-8") +addressutxos_s = subprocess.run("dash-cli %s getaddressutxos \'{\"addresses\":[\"%s\"]}\'" % (rpcargs, mn_address), shell=True, check=True, stdout=subprocess.PIPE).stdout.decode("utf-8") + +# print("blockchaininfo_s: %s" % blockchaininfo_s, file=sys.stderr) +# print("unspent_s: %s" % unspent_s, file=sys.stderr) +# print("addressutxos_s: %s" % addressutxos_s, file=sys.stderr) + +blockchaininfo = json.loads(blockchaininfo_s) +unspent = json.loads(unspent_s) +addressutxos = json.loads(addressutxos_s) + +tipHeight = blockchaininfo.get('blocks') - 1 + +# We have to look at both results from listunspent and getaddressutxos +# listunspent will not include already locked masternode UTXOs +# getaddressutxos will not include unconfirmed UTXOs +utxos = unspent +for u in addressutxos: + e = { + "txid": u.get('txid'), + "amount": u.get('satoshis') / COIN, + "vout": u.get('outputIndex'), + "confirmations": tipHeight - u.get('height') + } + # We might end up with duplicate entries, but who cares + utxos.append(e) + +best_txid = None +best_vout = None +best_conf = -1 + +for u in utxos: + if u.get('amount') == 1000: + better = best_txid is None + + if not better: + t1 = "%s-%d" % (u.get('txid'), u.get('vout')) + t2 = "%s-%d" % (best_txid, best_vout) + + c = u.get('confirmations') + if best_conf == c: + better = t1 < t2 + else: + better = c < best_conf + + if better: + best_txid = u.get('txid') + best_vout = u.get('vout') + best_conf = u.get('confirmations') + +if best_vout is None: + sys.exit(1) + +print(best_txid) +print(best_vout) + diff --git a/roles/mn-find-collateral/tasks/main.yml b/roles/mn-find-collateral/tasks/main.yml new file mode 100644 index 00000000..cd942413 --- /dev/null +++ b/roles/mn-find-collateral/tasks/main.yml @@ -0,0 +1,41 @@ +--- + +# Requires masternode and masternode_name to be set + +- name: copy script + copy: + src: scripts/find-collateral.py + dest: /usr/local/bin/find-collateral.py + owner: root + group: root + mode: 0755 + +- name: find-collateral for {{ masternode_name }}/{{ masternode.address }} + shell: find-collateral.py "{{ masternode_wallet_rpc_args }}" "{{ masternode.address }}" + register: r + failed_when: False + +- name: update collateral_ok + set_fact: + collateral_ok: '{{ r.rc == 0 }}' + collateral_txid: '' + collateral_vout: -1 + +- name: update txid/vout + set_fact: + collateral_txid: '{{ r.stdout_lines[0] }}' + collateral_vout: '{{ r.stdout_lines[1]|int }}' + when: collateral_ok + +- name: setting global masternode facts + set_fact: + collateral_ok_{{masternode_name|replace("-","_")}}: "{{ collateral_ok }}" + collateral_txid_{{masternode_name|replace("-","_")}}: "{{ collateral_txid }}" + collateral_vout_{{masternode_name|replace("-","_")}}: "{{ collateral_vout }}" + +- debug: msg="mn={{ masternode_name }} address={{ masternode.address }} collateral_ok={{collateral_ok}}, collateral_txid={{collateral_txid}}, collateral_vout={{collateral_vout}}" + +- name: updating collateral_ok_count + set_fact: + collateral_ok_count: "{{ collateral_ok_count|int + 1 }}" + when: collateral_ok diff --git a/roles/mn-fund-collateral/tasks/fund_collateral.yml b/roles/mn-fund-collateral/tasks/fund_collateral.yml new file mode 100644 index 00000000..d4fc2613 --- /dev/null +++ b/roles/mn-fund-collateral/tasks/fund_collateral.yml @@ -0,0 +1,12 @@ +--- + +- name: fund collateral for {{ masternode_name }}/{{ masternode.address }} + command: "dash-cli {{ faucet_rpc_args }} sendtoaddress {{ masternode.address }} 1000" + register: fund_result + +- name: wait for funding TX {{ fund_result.stdout }} to appear on MN ctrl node + shell: dash-cli {{ masternode_wallet_rpc_args }} getrawtransaction {{ fund_result.stdout }} + register: check_result + until: check_result.rc == 0 + retries: 10 + delay: 2 diff --git a/roles/mn-fund-collateral/tasks/main.yml b/roles/mn-fund-collateral/tasks/main.yml new file mode 100644 index 00000000..730ce17c --- /dev/null +++ b/roles/mn-fund-collateral/tasks/main.yml @@ -0,0 +1,71 @@ +--- + + +- name: import masternode funding address key + command: 'dash-cli {{ masternode_wallet_rpc_args }} importprivkey {{ masternodes[item].address_key }}' + with_items: '{{ groups["masternodes"] }}' + +# check + +- name: resetting collateral_ok_count + set_fact: + collateral_ok_count: 0 + +- include_role: + name: mn-find-collateral + vars: + masternode: "{{ masternodes[item] }}" + masternode_name: "{{ item }}" + with_items: '{{ groups["masternodes"] }}' + +- name: updating collateral_all_ok + set_fact: + collateral_all_ok: "{{ collateral_ok_count|int == groups['masternodes']|length }}" + +- debug: msg="collateral_ok_count={{ collateral_ok_count }}" + +# ensure faucet balance + +- name: generate enough blocks to fund collaterals + include_role: + name: generate-blocks + vars: + num_blocks: 0 + balance_needed: "{{ (groups['masternodes']|length - collateral_ok_count) * 1000 }}" + when: not collateral_all_ok + +# fund + +- include: fund_collateral.yml + vars: + masternode: "{{ masternodes[item] }}" + masternode_name: "{{ item }}" + when: not vars["collateral_ok_" + item|replace("-","_")] + with_items: '{{ groups["masternodes"] }}' + +# verify + +- name: resetting collateral_ok_count + set_fact: + collateral_ok_count: 0 + when: not collateral_all_ok + +- include_role: + name: mn-find-collateral + vars: + masternode: "{{ masternodes[item] }}" + masternode_name: "{{ item }}" + when: not collateral_all_ok + with_items: '{{ groups["masternodes"] }}' + +- fail: + msg: funding was not successful + when: not collateral_all_ok and collateral_ok_count|int != groups['masternodes']|length + +- name: generate at least one block to confirm funding transactions + include_role: + name: generate-blocks + vars: + num_blocks: 1 + balance_needed: 0 + when: not collateral_all_ok diff --git a/roles/mn-pay-faucet/tasks/main.yml b/roles/mn-pay-faucet/tasks/main.yml new file mode 100644 index 00000000..1b17a472 --- /dev/null +++ b/roles/mn-pay-faucet/tasks/main.yml @@ -0,0 +1,27 @@ +--- + +- name: copy pay-faucet script + template: + src: 'pay-faucet.sh.j2' + dest: '/usr/local/bin/pay-faucet.sh' + owner: 'root' + group: 'root' + mode: '0755' + register: service_result + +- name: create pay-faucet service + template: + src: 'pay-faucet.service.j2' + dest: '/etc/systemd/system/pay-faucet.service' + owner: 'root' + group: 'root' + mode: '0644' + register: service_result + +- name: enable pay-faucet + systemd: + name: 'pay-faucet' + state: restarted + enabled: True + masked: False + daemon_reload: '{{ service_result|changed }}' diff --git a/roles/mn-pay-faucet/templates/pay-faucet.service.j2 b/roles/mn-pay-faucet/templates/pay-faucet.service.j2 new file mode 100644 index 00000000..82dbc4ca --- /dev/null +++ b/roles/mn-pay-faucet/templates/pay-faucet.service.j2 @@ -0,0 +1,8 @@ +[Unit] +After=network.target + +[Service] +ExecStart=/usr/local/bin/pay-faucet.sh + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/roles/mn-pay-faucet/templates/pay-faucet.sh.j2 b/roles/mn-pay-faucet/templates/pay-faucet.sh.j2 new file mode 100644 index 00000000..16cba5cb --- /dev/null +++ b/roles/mn-pay-faucet/templates/pay-faucet.sh.j2 @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Regulary pays balance-mn_collaterals to faucet + +while true; do + BALANCE=$(dash-cli getbalance | awk '{print int($1)}') + if [ $BALANCE -gt 1 ]; then + echo "Total Balance: $BALANCE" + BALANCE=$(($BALANCE - 1)) # make sure we have enough for fee + BALANCE=$(($BALANCE - {{ groups['masternodes']|length * 1000 }})) # make sure we have enough for MN collaterals + echo "Send Balance: $BALANCE" + if [ $BALANCE -gt 0 ]; then + dash-cli sendtoaddress {{ faucet_address }} $BALANCE + fi + fi + + sleep 1m +done \ No newline at end of file diff --git a/roles/mn-sentinel/tasks/main.yml b/roles/mn-sentinel/tasks/main.yml new file mode 100644 index 00000000..7306d23d --- /dev/null +++ b/roles/mn-sentinel/tasks/main.yml @@ -0,0 +1,20 @@ +--- + +- name: create sentinel dirs + file: path='{{ item }}' state=directory recurse=true + with_items: + - /dash/sentinel/sentinel + +- name: sentinel config + template: + src: '{{ item }}' + dest: '/dash/sentinel/{{ item }}' + with_items: + - docker-compose.yml + +- name: sentinel service + docker_service: + project_src: /dash/sentinel + state: present + build: yes + pull: yes diff --git a/roles/mn-sentinel/templates/docker-compose.yml b/roles/mn-sentinel/templates/docker-compose.yml new file mode 100644 index 00000000..3871951d --- /dev/null +++ b/roles/mn-sentinel/templates/docker-compose.yml @@ -0,0 +1,13 @@ +version: '2' + +services: + sentinel: + image: ablock/sentinel + restart: always + environment: + - DEBUG=false + - RPCUSER={{ dashd_rpcuser }} + - RPCPASSWORD={{ dashd_rpcpassword }} + - RPCHOST={{ private_ip }} + - RPCPORT={{ dashd_rpcport }} + - NETWORK={{ dash_network }} diff --git a/roles/mn-start/defaults/main.yml b/roles/mn-start/defaults/main.yml new file mode 100644 index 00000000..f180dca9 --- /dev/null +++ b/roles/mn-start/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +start_type: missing \ No newline at end of file diff --git a/roles/mn-start/tasks/main.yml b/roles/mn-start/tasks/main.yml new file mode 100644 index 00000000..29db408b --- /dev/null +++ b/roles/mn-start/tasks/main.yml @@ -0,0 +1,23 @@ +--- + +- name: wait for mnsync to finish on masternode-wallet + shell: dash-cli mnsync status + register: status_result + until: status_result.rc == 0 and (status_result.stdout|from_json).IsSynced + retries: 100 + delay: 30 + +- name: wait for full mnsync to finish on masternodes + shell: dash-cli mnsync status + register: status_result + until: status_result.rc == 0 and (status_result.stdout|from_json).IsSynced + retries: 100 + delay: 30 + delegate_to: '{{ item }}' + with_items: '{{ groups["masternodes"] }}' + +- name: call "masternode start-{{ start_type }}" + command: dash-cli masternode start-{{ start_type }} + register: start_result + +- debug: var=start_result \ No newline at end of file diff --git a/roles/multifaucet/defaults/main.yml b/roles/multifaucet/defaults/main.yml new file mode 100644 index 00000000..849169c7 --- /dev/null +++ b/roles/multifaucet/defaults/main.yml @@ -0,0 +1,3 @@ +--- + + diff --git a/roles/multifaucet/tasks/main.yml b/roles/multifaucet/tasks/main.yml new file mode 100644 index 00000000..352a5dbd --- /dev/null +++ b/roles/multifaucet/tasks/main.yml @@ -0,0 +1,28 @@ +--- + +- name: import multifaucet donation address + command: dash-cli {{ faucet_rpc_args }} importprivkey {{ faucet_privkey }} + +- name: create multifaucet dirs + file: path='{{ item }}' state=directory recurse=true + with_items: + - /dash/multifaucet/multifaucet + +- name: multifaucet config + template: + src: '{{ item }}' + dest: '/dash/multifaucet/{{ item }}' + with_items: + - docker-compose.yml + - multifaucet/Dockerfile + - multifaucet/db.conf.php + - multifaucet/wallet.conf.php + - multifaucet/faucet.conf.php + - multifaucet/php.ini + - init.sql + +- name: multifaucet service + docker_service: + project_src: /dash/multifaucet + state: present + build: yes diff --git a/roles/multifaucet/templates/docker-compose.yml b/roles/multifaucet/templates/docker-compose.yml new file mode 100644 index 00000000..f099bd42 --- /dev/null +++ b/roles/multifaucet/templates/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3' + +services: + db: + image: mariadb:10.3 + restart: always + volumes: + - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro + environment: + - MYSQL_ROOT_PASSWORD=multifaucet + - MYSQL_DATABASE=multifaucet + - MYSQL_USER=multifaucet + - MYSQL_PASSWORD=multifaucet + #volumes: + # - /data/multifaucet/db:/var/lib/mysql + + multifaucet: + build: multifaucet + restart: always + environment: + - MULTIFAUCET_PAYMENT_GW_RPC_HOST={{ faucet_rpc_host }} + - MULTIFAUCET_PAYMENT_GW_RPC_PORT={{ faucet_rpc_port }} + - MULTIFAUCET_PAYMENT_GW_RPC_USER={{ faucet_rpc_user }} + - MULTIFAUCET_PAYMENT_GW_RPC_PASS={{ faucet_rpc_password }} + - MULTIFAUCET_ADDRESS_VERSION=140 + - MULTIFAUCET_DONATION_ADDRESS={{ faucet_address }} + ports: + - 80:80 diff --git a/roles/multifaucet/templates/init.sql b/roles/multifaucet/templates/init.sql new file mode 100644 index 00000000..3751f623 --- /dev/null +++ b/roles/multifaucet/templates/init.sql @@ -0,0 +1,14 @@ +CREATE TABLE `faucet_payouts` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip_address` VARCHAR(45) NOT NULL DEFAULT '', + `payout_amount` FLOAT NOT NULL, + `payout_address` VARCHAR(34) NOT NULL DEFAULT '', + `promo_code` VARCHAR(80) NOT NULL DEFAULT '', + `promo_payout_amount` FLOAT NOT NULL, + `txid` VARCHAR(80) NULL DEFAULT NULL, + `timestamp` DATETIME NOT NULL, + `lastupdate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) +COLLATE='utf8_general_ci' +ENGINE=MyISAM; diff --git a/roles/multifaucet/templates/multifaucet/Dockerfile b/roles/multifaucet/templates/multifaucet/Dockerfile new file mode 100644 index 00000000..317d2705 --- /dev/null +++ b/roles/multifaucet/templates/multifaucet/Dockerfile @@ -0,0 +1,38 @@ +FROM php:5.6-apache-jessie + +RUN apt-get update && apt-get install -y libpng12-0 libjpeg62-turbo libpng-dev libjpeg-dev libfreetype6 libfreetype6-dev \ + && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ + && docker-php-ext-install mysqli gd \ + && apt-get remove -y libpng-dev libjpeg-dev libfreetype6-dev \ + && rm -fr /var/cache/apt/* + +ENV MULTIFAUCET_COMMIT=5c625ee79be085c80f09098c6ee8213c0ef3c633 +RUN curl -L -o /tmp/multifaucet.tar.gz https://github.com/tuaris/multifaucet/archive/$MULTIFAUCET_COMMIT.tar.gz \ + && cd /tmp \ + && tar xzf multifaucet.tar.gz \ + && mv multifaucet-$MULTIFAUCET_COMMIT/* /var/www/html/ \ + && rm -rf multifaucet-$MULTIFAUCET_COMMIT multifaucet.tar.gz \ + && rm /var/www/html/install.php + +ADD *.conf.php /var/www/html/config/ +ADD *.ini /usr/local/etc/php/ +#RUN date > /var/www/html/config/.install_complete + +# Make configuration directory writable so the user can continue withe the web installer +RUN chown -R www-data:www-data /var/www/html/config/ +RUN chmod -R 700 /var/www/html/config/ + +# Make cold wallet directory writable so the user can continue with the the web installer +RUN mkdir -p /var/db/multifaucet && chown -R www-data:www-data /var/db/multifaucet/ +RUN chmod -R 700 /var/db/multifaucet/ + +ENV MULTIFAUCET_DB_HOST=db +ENV MULTIFAUCET_DB_NAME=multifaucet +ENV MULTIFAUCET_DB_USER=multifaucet +ENV MULTIFAUCET_DB_PASS=multifaucet +ENV MULTIFAUCET_DB_PRFX=faucet_ + +ENV MULTIFAUCET_PAYMENT_GW_RPC_USER=rpcuser +ENV MULTIFAUCET_PAYMENT_GW_RPC_PASS=rpcpass +ENV MULTIFAUCET_PAYMENT_GW_RPC_ENCR="" +ENV MULTIFAUCET_ADDRESS_VERSION=0 diff --git a/roles/multifaucet/templates/multifaucet/db.conf.php b/roles/multifaucet/templates/multifaucet/db.conf.php new file mode 100644 index 00000000..bde64c78 --- /dev/null +++ b/roles/multifaucet/templates/multifaucet/db.conf.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/roles/multifaucet/templates/multifaucet/faucet.conf.php b/roles/multifaucet/templates/multifaucet/faucet.conf.php new file mode 100644 index 00000000..76b96fe0 --- /dev/null +++ b/roles/multifaucet/templates/multifaucet/faucet.conf.php @@ -0,0 +1,49 @@ + getenv("MULTIFAUCET_DB_PRFX"), // table prefix to use + + "minimum_payout" => 1000, // minimum to be awarded + "maximum_payout" => 1000, // maximum to be awarded + "payout_threshold" => 1000, // payout threshold, if the faucet contains less than this, display the 'dry_faucet' message + "payout_interval" => "1m", // payout interval, the wait time for a user between payouts. Type any numerical value with either a "m" (minutes), "h" (hours), or "d" (days), attached. Examples: 50m for a 50 minute delay, 7h for a 7 hour delay, etc. + + // this option has 3 possible values: "ip_address", "wallet_address", and "both". It defines what to check for when a user enters an address in order to decide whether or not to award to this user. + // "ip_address": checks the user IP address in the payout history. + // "wallet_address": checks the wallet address in the payout history. + // "both": check both the IP and wallet address in the payout history. + "user_check" => "both", + + "use_captcha" => true, // require the user to enter a captcha + "use_spammerslapper" => false, // Prevent The use of Proxies and check the IP against Blacklists + + "captcha" => "simple-captcha", // which CAPTCHA to use, possible values are: "recaptcha", "solvemedia", and "simple-captcha". + + "captcha_config" => array( + //Simple Captcha Session Name + "simple_captcha_session_name" => "multifaucet", + // if you're using reCAPTCHA, enter your private and public keys here: + "recpatcha_private_key" => "PRIVATE_KEY_HERE", + "recpatcha_public_key" => "PUBLIC_KEY_HERE", + // if you're using Solve MEDIA, enter your private, challenge, and hash keys here: + "solvemedia_private_key" => "PRIVATE_KEY_HERE", + "solvemedia_challenge_key" => "CHALLENGE_KEY_HERE", + "solvemedia_hash_key" => "HASH_KEY_HERE", + ), + + "spammerslapper_key" => "", // SpammerSlapper API key. + + // promo codes: + "use_promo_codes" => false, // accept promo codes + + // Donation address: + "donation_address" => getenv("MULTIFAUCET_DONATION_ADDRESS"), // donation address to display + + // Faucet look and feel: + "title" => "Dash Faucet", // page title, may be used by the template too + "sitename" => "Dash Faucet", // page title, may be used by the template too + "sitedesc" => "Dash", // page title, may be used by the template too + "coin_code" => "DASH", + "template" => "default", // template to use (see the templates directory) + "lang" => "en", +); diff --git a/roles/multifaucet/templates/multifaucet/php.ini b/roles/multifaucet/templates/multifaucet/php.ini new file mode 100644 index 00000000..49f7dd69 --- /dev/null +++ b/roles/multifaucet/templates/multifaucet/php.ini @@ -0,0 +1,4 @@ +log_errors = On +error_log = /var/log/apache2/error.log + +date.timezone = UTC diff --git a/roles/multifaucet/templates/multifaucet/wallet.conf.php b/roles/multifaucet/templates/multifaucet/wallet.conf.php new file mode 100644 index 00000000..69155943 --- /dev/null +++ b/roles/multifaucet/templates/multifaucet/wallet.conf.php @@ -0,0 +1,11 @@ + diff --git a/roles/regtest-miner/tasks/main.yml b/roles/regtest-miner/tasks/main.yml new file mode 100644 index 00000000..e63edb9c --- /dev/null +++ b/roles/regtest-miner/tasks/main.yml @@ -0,0 +1,27 @@ +--- + +- name: copy regtest-miner script + template: + src: 'regtest-miner.sh.j2' + dest: '/usr/local/bin/regtest-miner.sh' + owner: 'root' + group: 'root' + mode: '0755' + register: service_result + +- name: create regtest-miner service + template: + src: 'regtest-miner.service.j2' + dest: '/etc/systemd/system/regtest-miner.service' + owner: 'root' + group: 'root' + mode: '0644' + register: service_result + +- name: enable regtest-miner + systemd: + name: 'regtest-miner' + state: restarted + enabled: True + masked: False + daemon_reload: '{{ service_result|changed }}' diff --git a/roles/regtest-miner/templates/regtest-miner.service.j2 b/roles/regtest-miner/templates/regtest-miner.service.j2 new file mode 100644 index 00000000..b69d5f07 --- /dev/null +++ b/roles/regtest-miner/templates/regtest-miner.service.j2 @@ -0,0 +1,10 @@ +[Unit] +After=network.target + +[Service] +User={{ dashd_user }} +Group={{ dashd_group }} +ExecStart=/usr/local/bin/regtest-miner.sh + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/roles/regtest-miner/templates/regtest-miner.sh.j2 b/roles/regtest-miner/templates/regtest-miner.sh.j2 new file mode 100644 index 00000000..9731789e --- /dev/null +++ b/roles/regtest-miner/templates/regtest-miner.sh.j2 @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +while true; do + dash-cli generate 1 + + {% if inventory_hostname != "faucet" %} + # Only needed when not run on same node as the faucet + BALANCE=$(dash-cli getbalance | awk '{print int($1)}') + if [ $BALANCE -gt 1 ]; then + echo "Total Balance: $BALANCE" + BALANCE=$(($BALANCE - 1)) # make sure we have enough for fee + echo "Send Balance: $BALANCE" + if [ $BALANCE -gt 0 ]; then + dash-cli sendtoaddress {{ miner_payment_address }} $BALANCE + fi + fi + {% endif %} + + sleep 2.5m +done \ No newline at end of file diff --git a/terraform/aws/inventory/ansible_inventory.tpl b/terraform/aws/inventory/ansible_inventory.tpl new file mode 100644 index 00000000..8edd53ed --- /dev/null +++ b/terraform/aws/inventory/ansible_inventory.tpl @@ -0,0 +1,25 @@ +${all_hosts} + +[web] +${web_hosts} + +[wallet-nodes] +${wallet_node_hosts} + +[full-nodes] +${full_node_hosts} + +[miners] +${miner_hosts} + +[masternodes] +${masternode_hosts} + +[seed-node] +node-1 + +[faucet-wallet] +dashd-wallet-1 + +[masternode-wallet] +dashd-wallet-2 diff --git a/terraform/aws/inventory/hostname.tpl b/terraform/aws/inventory/hostname.tpl new file mode 100644 index 00000000..f053e767 --- /dev/null +++ b/terraform/aws/inventory/hostname.tpl @@ -0,0 +1 @@ +${name} ansible_user='ubuntu' ansible_host=${public_ip} public_ip=${public_ip} private_ip=${private_ip} \ No newline at end of file diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf new file mode 100644 index 00000000..4b11dc79 --- /dev/null +++ b/terraform/aws/main.tf @@ -0,0 +1,268 @@ +# Specify the provider and access details +provider "aws" { + region = "${var.aws_region}" +} + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] # Canonical +} + +# Create a VPC to launch our instances into +resource "aws_vpc" "default" { + cidr_block = "10.0.0.0/16" +} + +# Create an internet gateway to give our subnet access to the outside world +resource "aws_internet_gateway" "default" { + vpc_id = "${aws_vpc.default.id}" +} + +# Grant the VPC internet access on its main route table +resource "aws_route" "internet_access" { + route_table_id = "${aws_vpc.default.main_route_table_id}" + destination_cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.default.id}" +} + +# Create a subnet to launch our instances into +resource "aws_subnet" "default" { + vpc_id = "${aws_vpc.default.id}" + cidr_block = "${var.private_subnet}" + map_public_ip_on_launch = true +} + +# A security group for the ELB so it is accessible via the web +resource "aws_security_group" "elb" { + name = "${var.dash_network}-${var.dash_devnet_name}-elb" + vpc_id = "${aws_vpc.default.id}" + + # HTTP access from anywhere + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # outbound internet access + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_security_group" "default" { + name = "${var.dash_network}-${var.dash_devnet_name}-ssh" + description = "dashd node" + vpc_id = "${aws_vpc.default.id}" + + # SSH access from anywhere + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # outbound internet access + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +# dashd nodes not accessible from the public internet +resource "aws_security_group" "dashd_private" { + name = "${var.dash_network}-${var.dash_devnet_name}-dashd-private" + description = "dashd private node" + vpc_id = "${aws_vpc.default.id}" + + # Dash Core access + ingress { + from_port = "${var.dashd_port}" + to_port = "${var.dashd_port}" + protocol = "tcp" + cidr_blocks = ["${var.private_subnet}"] + } + + # RPC access + ingress { + from_port = "${var.dashd_rpc_port}" + to_port = "${var.dashd_rpc_port}" + protocol = "tcp" + cidr_blocks = ["${var.private_subnet}"] + } +} + +# dashd node accessible from the public internet +resource "aws_security_group" "dashd" { + name = "${var.dash_network}-${var.dash_devnet_name}-dashd" + description = "dashd node" + vpc_id = "${aws_vpc.default.id}" + + # Dash Core access + ingress { + from_port = "${var.dashd_port}" + to_port = "${var.dashd_port}" + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # RPC access + ingress { + from_port = "${var.dashd_rpc_port}" + to_port = "${var.dashd_rpc_port}" + protocol = "tcp" + cidr_blocks = ["${var.private_subnet}"] + } +} + +resource "aws_security_group" "http" { + name = "${var.dash_network}-${var.dash_devnet_name}-http" + description = "dashd node" + vpc_id = "${aws_vpc.default.id}" + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["${var.private_subnet}"] + } +} + +resource "aws_elb" "web" { + name = "${var.dash_network}-${var.dash_devnet_name}" + + subnets = ["${aws_subnet.default.id}"] + security_groups = ["${aws_security_group.elb.id}"] + instances = ["${aws_instance.web.id}"] + + listener { + instance_port = 80 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +resource "aws_key_pair" "auth" { + key_name = "${var.dash_network}-${var.dash_devnet_name}-${var.key_name}" + public_key = "${file(var.public_key_path)}" +} + +# web +resource "aws_instance" "web" { + connection { + user = "ubuntu" + } + + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + key_name = "${aws_key_pair.auth.id}" + + vpc_security_group_ids = [ + "${aws_security_group.default.id}", + "${aws_security_group.http.id}", + ] + subnet_id = "${aws_subnet.default.id}" + + tags = { + Name = "${var.dash_network}-${var.dash_devnet_name}-web" + Hostname = "web" + } +} + +# dashd wallet nodes (for faucet and masternode collaterals) +resource "aws_instance" "dashd_wallet" { + count = "${var.wallet_count}" + + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + key_name = "${aws_key_pair.auth.id}" + + vpc_security_group_ids = [ + "${aws_security_group.default.id}", + "${aws_security_group.dashd_private.id}", + ] + subnet_id = "${aws_subnet.default.id}" + + tags = { + Name = "${var.dash_network}-${var.dash_devnet_name}-dashd-wallet-${count.index + 1}" + Hostname = "dashd-wallet-${count.index + 1}" + } +} + +# dashd full nodes +resource "aws_instance" "dashd_full_node" { + count = "${var.node_count}" + + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + key_name = "${aws_key_pair.auth.id}" + + vpc_security_group_ids = [ + "${aws_security_group.default.id}", + "${aws_security_group.dashd.id}", + ] + subnet_id = "${aws_subnet.default.id}" + + tags = { + Name = "${var.dash_network}-${var.dash_devnet_name}-node-${count.index + 1}" + Hostname = "node-${count.index + 1}" + } +} + +# cpu miners (not running a node) +resource "aws_instance" "miner" { + count = "${var.miner_count}" + + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.small" + key_name = "${aws_key_pair.auth.id}" + + vpc_security_group_ids = [ + "${aws_security_group.default.id}" + ] + subnet_id = "${aws_subnet.default.id}" + + tags = { + Name = "${var.dash_network}-${var.dash_devnet_name}-miner-${count.index + 1}" + Hostname = "miner-${count.index + 1}" + } +} + +# masternodes +resource "aws_instance" "masternode" { + count = "${var.masternode_count}" + + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + key_name = "${aws_key_pair.auth.id}" + + vpc_security_group_ids = [ + "${aws_security_group.default.id}", + "${aws_security_group.dashd.id}", + ] + subnet_id = "${aws_subnet.default.id}" + + tags = { + Name = "${var.dash_network}-${var.dash_devnet_name}-masternode-${count.index + 1}" + Hostname = "masternode-${count.index + 1}" + } +} diff --git a/terraform/aws/outputs.tf b/terraform/aws/outputs.tf new file mode 100644 index 00000000..c3151904 --- /dev/null +++ b/terraform/aws/outputs.tf @@ -0,0 +1,73 @@ + +data "template_file" "web_hosts" { + count = "${aws_instance.web.count}" + template = "${file("${path.module}/inventory/hostname.tpl")}" + vars { + index = "${count.index + 1}" + name = "${element(aws_instance.web.*.tags.Hostname, count.index)}" + public_ip = "${element(aws_instance.web.*.public_ip, count.index)}" + private_ip = "${element(aws_instance.web.*.private_ip, count.index)}" + } +} +data "template_file" "wallet_node_hosts" { + count = "${aws_instance.dashd_wallet.count}" + template = "${file("${path.module}/inventory/hostname.tpl")}" + vars { + index = "${count.index + 1}" + name = "${element(aws_instance.dashd_wallet.*.tags.Hostname, count.index)}" + public_ip = "${element(aws_instance.dashd_wallet.*.public_ip, count.index)}" + private_ip = "${element(aws_instance.dashd_wallet.*.private_ip, count.index)}" + } +} +data "template_file" "full_node_hosts" { + count = "${aws_instance.dashd_full_node.count}" + template = "${file("${path.module}/inventory/hostname.tpl")}" + vars { + index = "${count.index + 1}" + name = "${element(aws_instance.dashd_full_node.*.tags.Hostname, count.index)}" + public_ip = "${element(aws_instance.dashd_full_node.*.public_ip, count.index)}" + private_ip = "${element(aws_instance.dashd_full_node.*.private_ip, count.index)}" + } +} +data "template_file" "miner_hosts" { + count = "${aws_instance.miner.count}" + template = "${file("${path.module}/inventory/hostname.tpl")}" + vars { + index = "${count.index + 1}" + name = "${element(aws_instance.miner.*.tags.Hostname, count.index)}" + public_ip = "${element(aws_instance.miner.*.public_ip, count.index)}" + private_ip = "${element(aws_instance.miner.*.private_ip, count.index)}" + } +} +data "template_file" "masternode_hosts" { + count = "${aws_instance.masternode.count}" + template = "${file("${path.module}/inventory/hostname.tpl")}" + vars { + index = "${count.index + 1}" + name = "${element(aws_instance.masternode.*.tags.Hostname, count.index)}" + public_ip = "${element(aws_instance.masternode.*.public_ip, count.index)}" + private_ip = "${element(aws_instance.masternode.*.private_ip, count.index)}" + } +} + + +data "template_file" "ansible_inventory" { + template = "${file("${path.module}/inventory/ansible_inventory.tpl")}" + vars { + all_hosts = "${join("\n",concat( + data.template_file.web_hosts.*.rendered, + data.template_file.wallet_node_hosts.*.rendered, + data.template_file.full_node_hosts.*.rendered, + data.template_file.miner_hosts.*.rendered, + data.template_file.masternode_hosts.*.rendered))}" + web_hosts = "${join("\n", concat(aws_instance.web.*.tags.Hostname))}" + wallet_node_hosts = "${join("\n", concat(aws_instance.dashd_wallet.*.tags.Hostname))}" + full_node_hosts = "${join("\n", concat(aws_instance.dashd_full_node.*.tags.Hostname))}" + miner_hosts = "${join("\n", concat(aws_instance.miner.*.tags.Hostname))}" + masternode_hosts = "${join("\n", concat(aws_instance.masternode.*.tags.Hostname))}" + } +} + +output "ansible_inventory" { + value = "${data.template_file.ansible_inventory.rendered}" +} diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf new file mode 100644 index 00000000..afec2f2a --- /dev/null +++ b/terraform/aws/variables.tf @@ -0,0 +1,50 @@ +variable "public_key_path" { + default = "~/.ssh/id_rsa.pub" +} + +variable "key_name" { + description = "Desired name of AWS key pair" + default = "dash_cluster_key" +} + +variable "aws_region" { + description = "AWS region to launch servers." + default = "eu-west-1" +} + +variable "dash_network" { + description = "Dash network to create or connect to (mainnet/testnet/devnet/regtest)" +} + +variable "dash_devnet_name" { + description = "Dash devnet name" +} + +variable "dashd_port" { + description = "Port for Dash Core nodes" +} + +variable "dashd_rpc_port" { + description = "Port for Dash RPC interface" +} + +variable "private_subnet" { + default = "10.0.0.0/16" +} + +variable "node_count" { + default = 1 +} + +variable "miner_count" { + default = 1 +} + +variable "masternode_count" { + default = 1 +} + +variable "wallet_count" { + description = "number of wallet nodes to create. must be at least 2" + default = 2 +} \ No newline at end of file