diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4af4b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +resources/playbook/env/ +venv/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f1f634 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# CRUN + +CRUN is a simple command line tool to spin up blockchain node. It is designed to be simple and easy to use. + +## Installation + +To install simply download the latest release from the [releases page]() + +## Usage + + +### Install a blockchain node example + +```bash +crun install lava_testnet +``` + +### Change default settings + +1. Show the current settings for network + +```bash +crun show lava_testnet +``` + +2. You can check how setting will be changed by running the following command + +```bash +crun show lava_testnet -e "install_from=state_sync" +``` + +3. Run install with new settings + +```bash +crun install lava_testnet -e "install_from=state_sync" +``` + +## Install crun from source + +1. Clone the repository + +2. Create venv and install dependencies + +```bash +python3 -m venv venv +source venv/bin/activate +pip install . +``` +3. Run crun + +```bash +python crun.py +``` diff --git a/crun.py b/crun.py new file mode 100644 index 0000000..fbca2ae --- /dev/null +++ b/crun.py @@ -0,0 +1,141 @@ +import os +import sys +# set the path for ansible +new_path = sys.exec_prefix + '/bin' +current_path = os.environ.get('PATH', '') +os.environ['PATH'] = new_path + os.pathsep + current_path + +import json + +from pygments import highlight +from pygments.formatters import TerminalFormatter +from pygments.lexers import PythonLexer +from pygments.formatters import TerminalFormatter + +import click +import tempfile +from pathlib import Path +import yaml +import ansible_runner + +def pretty_print_with_highlight(data): + """Pretty print a Python dictionary with syntax highlighting.""" + # Convert the dictionary to a formatted string + formatted_str = json.dumps(data, indent=4) + # Apply syntax highlighting using Pygments + return highlight(formatted_str, PythonLexer(), TerminalFormatter()) + +def running_from_pex() -> bool: + pex_root = os.environ.get("PEX_ROOT", os.path.expanduser("~/.pex")) + return any([pex_root in p for p in sys.path]) + +def print_error(msg): + click.echo(click.style(msg, fg='red'), err=True) + +def convert_extra_vars(extra_vars): + extra_vars_dict = {} + extra_vars = filter(lambda x: x.strip() != '', extra_vars.split(';')) + + for var in extra_vars: + key, value = var.strip().split('=') + if value.strip().isdigit(): + value = int(value) + if type(value) == str and value.strip().lower() == 'true': + value = True + if type(value) == str and value.strip().lower() == 'false': + value = False + extra_vars_dict[key] = value + return extra_vars_dict + +default_vars = { + 'node_name': "CRUN_MELLIFERA_NODE", + 'download_cosmovisor_url': 'https://storage.mellifera.network/bins/general/cosmovisor', + 'download_cosmovisor': True, + 'install_from': 'snapshot' +} + +def get_playbook_folder(): + is_pex = running_from_pex() + current_dir = Path(__file__).parent + if is_pex: + return current_dir / 'playbook' + else: + return current_dir / 'resources/playbook' + +@click.group() +def cli(): + pass + +@cli.command() +def list(): + playbook_folder = get_playbook_folder() + list = os.listdir(playbook_folder / 'group_vars') + click.echo('List of available networks:') + click.echo('---------------------------') + + for network in list: + netname = network.split('.')[0] + if netname != 'all': + click.echo(netname) + +def get_config_by_netname(netname): + playbook_folder = get_playbook_folder() + list_files = os.listdir(playbook_folder / 'group_vars') + netnames = [{ "name": network.split('.')[0], "file": network } for network in list_files] + + if netname not in map(lambda x: x['name'], netnames): + return None + netfile = next(filter(lambda x: x['name'] == netname, netnames))['file'] + + with open(playbook_folder / 'group_vars' / netfile) as f: + content = f.read() + return yaml.full_load(content) + +@cli.command() +@click.argument('netname') +@click.option('--extra-vars','-e', type=str , help="Show the configuration with the extra vars. Example: --extra-vars 'node_name=MELLIFERA;use_state_sync=true'") +def show(netname, extra_vars): + ext = {} + if extra_vars is not None: + ext = convert_extra_vars(extra_vars) + + config = get_config_by_netname(netname) + if config is None: + print_error('Network %s not found' % netname) + return + final_config = {**default_vars, **config, **ext} + click.echo('------ %s config ------' % netname) + click.echo(pretty_print_with_highlight(final_config)) + click.echo('------ %s config ------' % netname) + +@cli.command() +@click.argument('network_name') +@click.option('--extra-vars','-e', + type=str , + help="Run the installation with the overrided vars. Example: --extra-vars 'node_name=MELLIFERA;install_from=state_sync'") +def install(network_name, extra_vars): + playbook_folder = get_playbook_folder() + ext = {} + if extra_vars is not None: + ext = convert_extra_vars(extra_vars) + config = get_config_by_netname(network_name) + if config is None: + print_error('Network %s not found' % network_name) + return + final_config = {**default_vars, **config, **ext} + if os.path.exists(final_config['cosmos_folder']): + print_error('Cosmos folder %s already exists. Please remove it before running the installation or change "cosmos_folder" var with --extra-var option' % final_config['cosmos_folder']) + return + + if os.geteuid() != 0: + print_error('Installation requires root privileges. Creating services file and start systemd unit.\nPlease run: sudo crun install %s' % network_name) + return + + click.echo('Running installation with the following configuration:') + click.echo(pretty_print_with_highlight(final_config)) + with tempfile.TemporaryDirectory() as temp_dir_artifacts: + ansible_runner.run(private_data_dir=playbook_folder, artifact_dir=temp_dir_artifacts, playbook='local.yml', passwords={}, extravars=final_config) + + +if __name__ == '__main__': + cli() diff --git a/resources/playbook/group_vars/lava_testnet.yaml b/resources/playbook/group_vars/lava_testnet.yaml new file mode 100644 index 0000000..3f5018d --- /dev/null +++ b/resources/playbook/group_vars/lava_testnet.yaml @@ -0,0 +1,23 @@ +chain_id: lava-testnet-2 +netname: lava_testnet +repo: https://github.com/lavanet/lava.git +version: v2.0.0 +binary: lavad +genesis: https://storage.mellifera.network/lava_testnet/genesis.json +addressbook: https://storage.mellifera.network/lava_testnet/addrbook.json +snapshot: https://storage.mellifera.network/lava_testnet/snapshot_latest.tar.lz4 +go_version: go1.20.14 +cosmos_folder: '.lava' +seeds: "e28b8ad6e20fda1e647c977fb256208b91b74893@116.202.80.186:14456" +peers: e29acb7239b0532cc7134befe187e83e0c84c3bc@62.171.135.97:24656,2044ce5fc86c2d2b418e50421abc18bde5f952cd@144.91.99.234:656,f601fe23f13ec16057c14701feb15b7b424c5fdb@109.199.101.81:14456,74a6baff434b8c7189b8d8476c762449cc2a70e6@213.199.57.18:14456,d5519e378247dfb61dfe90652d1fe3e2b3005a5b@65.109.68.190:14456,dae571b14dcb4e55566071cb0083a937edd0cfe8@109.199.116.71:19956,9595f5ed6f97b0a9a4088c0a235356d0d2d77167@31.220.90.190:24656,802e15de52338029c6e2de2901c8cdd75f15ee9b@64.120.88.81:23656,5c2a752c9b1952dbed075c56c600c3a79b58c395@195.3.220.21:27066,e49bd2f4ebe95ccf198cc997151a9389a7482411@167.235.115.119:36656,f6a2359abadba6b22544658a3492aed84b4e26b4@143.110.185.169:26656,3306e10f1635f71e1d93219a369f4907ec062ad5@167.235.14.83:17656,3dd292d048d215a05687c6655c189faf82f74b2f@38.242.241.7:24656,111eb709143ec92fdad9d819d4a454e4f0bc79c0@15.235.204.150:21156,b610a79e26758553ec637c57a722a4d9503b4e29@167.86.102.68:14456,e33ea4c7611bfee8160ad577890a9ae158a9a3ac@95.217.200.98:19656 +custom_port_prefix: 199 +service_name: lavad +download_binary_url: "https://storage.mellifera.network/bins/lava-testnet/{{ version }}/lavad" + +state_sync_rpc: "https://lava-testnet-rpc.mellifera.network:443" +state_sync_peer: "e28b8ad6e20fda1e647c977fb256208b91b74893@116.202.80.186:14456" + +download_binary: true +download_cosmovisor: true + + diff --git a/resources/playbook/local.yml b/resources/playbook/local.yml new file mode 100644 index 0000000..4bcbbc7 --- /dev/null +++ b/resources/playbook/local.yml @@ -0,0 +1,10 @@ +- name: Install node locally + hosts: localhost + become: true # Enables privilege escalation + roles: + - cosmos_software + - cosmos_install + - cosmos_init + - cosmos_configure + - cosmos_start + diff --git a/resources/playbook/roles/cosmos_configure/tasks/main.yml b/resources/playbook/roles/cosmos_configure/tasks/main.yml new file mode 100644 index 0000000..7fbb37f --- /dev/null +++ b/resources/playbook/roles/cosmos_configure/tasks/main.yml @@ -0,0 +1,81 @@ +--- +- name: register public ip + uri: + url: 'https://api.ipify.org?format=json' + register: public_ip + +- name: Set up external address + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml' + regexp: 'external_address = "' + line: 'external_address = "{{ public_ip.json.ip }}:{{ custom_port_prefix }}56"' + state: present + +- name: Adjust config.toml port + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml' + regexp: '{{ item.key }}' + line: '{{ item.value }}' + loop: '{{ config_port_changes | dict2items }}' + +- name: Adjust app.toml ports + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/app.toml' + regexp: '{{ item.key }}' + line: '{{ item.value }}' + backrefs: true + loop: '{{ app_port_changes | dict2items }}' + +- name: Set pruning settings + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/app.toml' + regexp: '{{ item.key }}' + line: '{{ item.value }}' + loop: '{{ pruning_default | dict2items }}' + when: type is not defined + +- name: Adjust pruning setting for relayer + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/app.toml' + regexp: '{{ item.key }}' + line: '{{ item.value }}' + loop: '{{ pruning_relayer | dict2items }}' + when: type is defined and type == 'relayer' + +- name: Dowload addrbook + ansible.builtin.get_url: + url: '{{ addrbook }}' + dest: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/addrbook.json' + mode: '0644' + force: true + when: addrbook is defined + +- name: Update peers in config.toml file + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml' + regexp: '^persistent_peers =' + line: 'persistent_peers = "{{ peers }}"' + state: present + when: peers is defined + +- name: Update seeds in config.toml file + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder}}/config/config.toml' + regexp: '^seeds =' + line: 'seeds = "{{ seeds }}"' + state: present + when: seeds is defined + +- name: Check if after config script exists + ansible.builtin.stat: + path: '{{ role_path }}/templates/after_config_{{ netname }}.sh.j2' + register: after_config_script + delegate_to: localhost + +- name: execute after config script + shell: "{{ lookup('template', 'after_config_{{ netname }}.sh.j2') }}" + args: + chdir: '{{ ansible_env.HOME }}/{{ cosmos_folder }}' + executable: /bin/bash + when: after_config_script.stat.exists + diff --git a/resources/playbook/roles/cosmos_configure/templates/after_config_lava_testnet.sh.j2 b/resources/playbook/roles/cosmos_configure/templates/after_config_lava_testnet.sh.j2 new file mode 100644 index 0000000..9faf822 --- /dev/null +++ b/resources/playbook/roles/cosmos_configure/templates/after_config_lava_testnet.sh.j2 @@ -0,0 +1,10 @@ +sed -i \ + -e 's/timeout_commit = ".*"/timeout_commit = "30s"/g' \ + -e 's/timeout_propose = ".*"/timeout_propose = "1s"/g' \ + -e 's/timeout_precommit = ".*"/timeout_precommit = "1s"/g' \ + -e 's/timeout_precommit_delta = ".*"/timeout_precommit_delta = "500ms"/g' \ + -e 's/timeout_prevote = ".*"/timeout_prevote = "1s"/g' \ + -e 's/timeout_prevote_delta = ".*"/timeout_prevote_delta = "500ms"/g' \ + -e 's/timeout_propose_delta = ".*"/timeout_propose_delta = "500ms"/g' \ + -e 's/skip_timeout_commit = ".*"/skip_timeout_commit = false/g' \ + {{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml diff --git a/resources/playbook/roles/cosmos_configure/vars/main.yml b/resources/playbook/roles/cosmos_configure/vars/main.yml new file mode 100644 index 0000000..15ea6db --- /dev/null +++ b/resources/playbook/roles/cosmos_configure/vars/main.yml @@ -0,0 +1,24 @@ +config_port_changes: + 'laddr = "tcp://0.0.0.0:26656"': 'laddr = "tcp://0.0.0.0:{{ custom_port_prefix }}56"' + 'laddr = "tcp://127.0.0.1:26657"': 'laddr = "tcp://0.0.0.0:{{ custom_port_prefix }}57"' + 'proxy_app = "tcp://127.0.0.1:26658"': 'proxy_app = "tcp://127.0.0.1:{{ custom_port_prefix }}58"' + 'prometheus_listen_addr = ":26660"': 'prometheus_listen_addr = ":{{ custom_port_prefix }}61"' + 'pprof_laddr = "localhost:6060"': 'pprof_laddr = "localhost:{{ custom_port_prefix }}60"' + +app_port_changes: + ':1317': 'address = "tcp://0.0.0.0:{{ custom_port_prefix }}17"' + ':8080': 'address = ":{{ custom_port_prefix }}80"' + ':9090': 'address = "0.0.0.0:{{ custom_port_prefix }}90"' + ':9091': 'address = "0.0.0.0:{{ custom_port_prefix }}91"' + +pruning_default: + 'pruning = "': 'pruning = "custom"' + 'pruning-keep-recent = "': 'pruning-keep-recent = "100"' + 'pruning-interval = "': 'pruning-interval = "19"' + +pruning_relay: + 'pruning = "': 'pruning = "custom"' + 'pruning-keep-recent = "': 'pruning-keep-recent = "100"' + 'pruning-interval = "': 'pruning-interval = "19"' + 'snapshot-interval = ': 'snapshot-interval = 1000' + 'snapshot-keep-recent = ': 'snapshot-keep-recent = 2' diff --git a/resources/playbook/roles/cosmos_init/tasks/main.yaml b/resources/playbook/roles/cosmos_init/tasks/main.yaml new file mode 100644 index 0000000..b87cea0 --- /dev/null +++ b/resources/playbook/roles/cosmos_init/tasks/main.yaml @@ -0,0 +1,23 @@ +--- +- name: Set cosmos binary + ansible.builtin.set_fact: + cosmos_binary: "{{ ansible_env.HOME }}/.crun/bins/{{ netname }}/{{ version }}/{{ binary }}" +- name: Ensure that the folder does not exist + ansible.builtin.file: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}' + state: absent + +- name: Init Node + ansible.builtin.command: + cmd: '{{ cosmos_binary }} init {{ node_name }} --home={{ ansible_env.HOME }}/{{ cosmos_folder }} --chain-id {{ chain_id }}' + +- name: Copy genesis.json + ansible.builtin.get_url: + url: '{{ genesis }}' + dest: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/genesis.json' + mode: '0644' + force: true + +- name: Set chain-id + ansible.builtin.command: + cmd: '{{cosmos_binary}} config chain-id {{ chain_id }} --home={{ ansible_env.HOME }}/{{ cosmos_folder }}' diff --git a/resources/playbook/roles/cosmos_init/vars/main.yaml b/resources/playbook/roles/cosmos_init/vars/main.yaml new file mode 100644 index 0000000..a53f942 --- /dev/null +++ b/resources/playbook/roles/cosmos_init/vars/main.yaml @@ -0,0 +1 @@ +cosmos_binary: "{{ ansible_env.HOME }}/ansible_build/{{ netname }}/{{ version }}/{{ binary }}" diff --git a/resources/playbook/roles/cosmos_install/tasks/main.yml b/resources/playbook/roles/cosmos_install/tasks/main.yml new file mode 100644 index 0000000..4a90b25 --- /dev/null +++ b/resources/playbook/roles/cosmos_install/tasks/main.yml @@ -0,0 +1,75 @@ +--- + +- name: Fail if download_binary is true and download_binary_url is not defined + fail: + msg: "download_binary is true but download_binary_url is not defined" + when: download_binary is true and download_binary_url is not defined + +- name: Clone the repository + ansible.builtin.git: + repo: "{{ repo }}" + dest: "{{ ansible_env.HOME }}/{{ netname }}" + version: "{{ version }}" + update: true + + +- name: Create ansible_build directory + ansible.builtin.file: + path: "{{ build_dir }}/{{ version }}" + state: directory + mode: '0755' + +- name: Build the binary + block: + - name: Check if specific build script template exists + ansible.builtin.stat: + path: "{{ role_path }}/templates/build_{{ netname }}.sh.j2" + register: specific_template + delegate_to: localhost + + - name: Set fact for template to use + ansible.builtin.set_fact: + template_to_use: "{{ 'build_' + netname + '.sh.j2' if specific_template.stat.exists else 'default.sh.j2' }}" + + - name: Prepare build script + ansible.builtin.template: + src: "{{ template_to_use }}" + dest: "{{ build_dir }}/build_{{ netname }}.sh" + mode: '0755' + + - name: Build the binary + ansible.builtin.shell: "bash {{ build_dir }}/build_{{ netname }}.sh" + args: + chdir: "{{ ansible_env.HOME }}/{{ netname }}" + executable: /bin/bash + creates: "{{ build_dir }}/{{ version }}/{{ binary }}" + when: download_binary is undefined or download_binary == false + + + + +- name: Download binary + block: + - name: Check if cosmosd binary exists + ansible.builtin.stat: + path: "{{ build_dir }}/{{ version }}/{{ binary }}" + register: is_cosmos_binary + + - name: Ensure the directory exists + ansible.builtin.file: + path: "{{ build_dir }}/{{ version }}" + state: directory + mode: '0755' + become: true + + - name: Download binary + become: true + ansible.builtin.get_url: + url: "{{ download_binary_url }}" + dest: "{{ build_dir }}/{{ version }}/{{ binary }}" + mode: '0755' + when: + - download_binary is true + - not is_cosmos_binary.stat.exists + + diff --git a/resources/playbook/roles/cosmos_install/templates/build_lava_testnet.sh.j2 b/resources/playbook/roles/cosmos_install/templates/build_lava_testnet.sh.j2 new file mode 100644 index 0000000..8b9e334 --- /dev/null +++ b/resources/playbook/roles/cosmos_install/templates/build_lava_testnet.sh.j2 @@ -0,0 +1,8 @@ +source {{ ansible_env.HOME }}/.gvm/scripts/gvm +gvm install {{ go_version }} -B +gvm use {{ go_version }} --default +export LAVA_BINARY=lavad +mkdir -p {{build_dir}}/{{version }} +make build +mv build/{{ binary }} {{build_dir}}/{{ version }} + diff --git a/resources/playbook/roles/cosmos_install/templates/default.sh.j2 b/resources/playbook/roles/cosmos_install/templates/default.sh.j2 new file mode 100644 index 0000000..5f98d69 --- /dev/null +++ b/resources/playbook/roles/cosmos_install/templates/default.sh.j2 @@ -0,0 +1,8 @@ +source {{ ansible_env.HOME }}/.gvm/scripts/gvm +gvm install {{ go_version }} -B +gvm use {{ go_version }} --default +export LAVA_BINARY={{ binary }} +mkdir -p {{build_dir}}/{{version }} +make build +mv build/{{ binary }} {{build_dir}}/{{ version }} + diff --git a/resources/playbook/roles/cosmos_install/vars/main.yaml b/resources/playbook/roles/cosmos_install/vars/main.yaml new file mode 100644 index 0000000..d863a5d --- /dev/null +++ b/resources/playbook/roles/cosmos_install/vars/main.yaml @@ -0,0 +1,2 @@ +build_dir: "{{ ansible_env.HOME }}/.crun/bins/{{ netname }}" + diff --git a/resources/playbook/roles/cosmos_software/tasks/main.yml b/resources/playbook/roles/cosmos_software/tasks/main.yml new file mode 100644 index 0000000..414e200 --- /dev/null +++ b/resources/playbook/roles/cosmos_software/tasks/main.yml @@ -0,0 +1,58 @@ +--- + +- name: Fail if download_cosmovisor is true and download_cosmovisor_url is not defined + fail: + msg: "download_cosmovisor is true but download_cosmovisor_url is not defined" + when: download_cosmovisor is true and download_cosmovisor_url is not defined + +- name: Install packages + become: true + ansible.builtin.apt: + pkg: + - bison + - jq + - make + - build-essential + - git + - lz4 + - tar + state: present + update_cache: true + +- name: Install GVM (Go Version Manager) + ansible.builtin.shell: > + bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) + args: + creates: "{{ ansible_env.HOME }}/.gvm" + executable: /bin/bash + +- name: Install cosmovisor + ansible.builtin.shell: | + source {{ ansible_env.HOME }}/.gvm/scripts/gvm + gvm install go1.20.14 -B + gvm use go1.20.14 --default + go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.5.0 + sudo mv $(which cosmovisor) /usr/local/bin/cosmovisor + args: + executable: /bin/bash + creates: /usr/local/bin/cosmovisor + when: download_cosmovisor is false + +- name: Download cosmovisor + block: + - name: Check if cosmosd binary exists + ansible.builtin.stat: + path: /usr/local/bin/cosmovisor + register: is_cosmosvisor_binary + + - name: Download cosmovisor + become: true + ansible.builtin.get_url: + url: "{{ download_cosmovisor_url }}" + dest: /usr/local/bin/cosmovisor + mode: '0755' + when: + - download_cosmovisor is true + - not is_cosmosvisor_binary.stat.exists + + diff --git a/resources/playbook/roles/cosmos_start/tasks/main.yml b/resources/playbook/roles/cosmos_start/tasks/main.yml new file mode 100644 index 0000000..60602ab --- /dev/null +++ b/resources/playbook/roles/cosmos_start/tasks/main.yml @@ -0,0 +1,115 @@ +- name: open p2p port + become: true + command: 'ufw allow {{ custom_port_prefix }}56/tcp' + +- name: Check if install_from is 'snapshot' and snapshot is not defined + fail: + msg: "Snapshot is not defined" + when: install_from is defined and install_from == snapshot and snapshot is not defined + +- name: Check if install_from is 'state_sync' and state_sync_rpc is not defined + fail: + msg: "State sync rpc is not defined" + when: install_from is defined and install_from == 'state_sync' and state_sync_rpc is not defined + +- name: Check if only one of use_snapshot and use_state_sync + fail: + msg: "install_from is required can be only 'snapshot' or 'state_sync'" + when: install_from is not defined or (install_from != 'snapshot' and install_from != 'state_sync') + +- name: Download snapshot + ansible.builtin.shell: | + rm -rf {{ ansible_env.HOME }}/{{ cosmos_folder }}/data + curl -L {{ snapshot }} | tar -Ilz4 -xf - -C {{ ansible_env.HOME }}/{{ cosmos_folder }} + when: install_from == 'snapshot' + register: snapshot_download + async: 3000 + poll: 0 + args: + executable: /bin/bash + +- name: Download cosmo wasm + ansible.builtin.shell: | + curl -L {{ wasm }} | lz4 -dc - | tar -xf - -C {{ ansible_env.HOME }}/{{ cosmos_folder }} + args: + executable: /bin/bash + when: wasm is defined + +- name: Wait for snapshot download + ansible.builtin.async_status: + jid: "{{ snapshot_download.ansible_job_id }}" + register: job_status + until: job_status.finished + retries: 1000 + delay: 5 + when: install_from == 'snapshot' + +- name: Use state sync + block: + - name: Use state sync peer + lineinfile: + path: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml' + regexp: '^persistent_peers =' + line: 'persistent_peers = "{{ state_sync_peer }}"' + state: present + when: state_sync_peer is defined + + - name: Apply state sync settings + shell: "{{ lookup('template', 'use_state_sync.sh.j2') }}" + args: + executable: /bin/bash + when: install_from == 'state_sync' + +- name: Create cosmovisor directories + ansible.builtin.file: + path: '{{ item }}' + state: directory + mode: '0755' + with_items: + - '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/genesis/bin' + - '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/upgrades' + +- name: Create app symlink + ansible.builtin.file: + src: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/genesis' + dest: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/current' + state: link + force: true + become: true + +- name: Create app symlink 2 + ansible.builtin.file: + src: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/current/bin/{{ binary }}' + dest: '/usr/local/bin/{{ binary }}' + state: link + force: true + become: true + +- name: Set local node + ansible.builtin.command: + cmd: '{{cosmos_binary}} config node tcp://localhost:{{ custom_port_prefix }}57 --home={{ ansible_env.HOME }}/{{ cosmos_folder }}' + changed_when: false + +- name: Copy daemon file to cosmovisor + ansible.builtin.copy: + src: '{{ ansible_env.HOME }}/.crun/bins/{{ netname }}/{{ version }}/{{ binary}}' + dest: '{{ ansible_env.HOME }}/{{ cosmos_folder }}/cosmovisor/genesis/bin' + remote_src: true + mode: '0755' + +- name: Copy service file + become: true + ansible.builtin.template: + src: 'cosmovisor.service.j2' + dest: '/etc/systemd/system/{{ service_name }}.service' + owner: root + group: root + mode: '0644' + +- name: Start cosmovisor service + become: true + ansible.builtin.systemd: + name: '{{ service_name }}' + state: restarted + daemon_reload: true + enabled: true diff --git a/resources/playbook/roles/cosmos_start/templates/cosmovisor.service.j2 b/resources/playbook/roles/cosmos_start/templates/cosmovisor.service.j2 new file mode 100644 index 0000000..b2b37ad --- /dev/null +++ b/resources/playbook/roles/cosmos_start/templates/cosmovisor.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=cosmovisor {{ service_name }} +After=network-online.target + +[Service] +User={{ ansible_env.USER }} +ExecStart=/usr/local/bin/cosmovisor run start --home {{ ansible_env.HOME }}/{{ cosmos_folder }} +Restart=always +RestartSec=3 +LimitNOFILE=4096 +Environment="DAEMON_NAME={{ binary }}" +Environment="DAEMON_HOME={{ ansible_env.HOME }}/{{ cosmos_folder }}" +Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=true" +Environment="DAEMON_RESTART_AFTER_UPGRADE=true" +Environment="UNSAFE_SKIP_BACKUP=true" + +[Install] +WantedBy=multi-user.target diff --git a/resources/playbook/roles/cosmos_start/templates/use_state_sync.sh.j2 b/resources/playbook/roles/cosmos_start/templates/use_state_sync.sh.j2 new file mode 100644 index 0000000..0afd00e --- /dev/null +++ b/resources/playbook/roles/cosmos_start/templates/use_state_sync.sh.j2 @@ -0,0 +1,9 @@ +STATE_SYNC_RPC="{{ state_sync_rpc }}" +LATEST_HEIGHT=$(curl -s $STATE_SYNC_RPC/block | jq -r .result.block.header.height); \ +BLOCK_HEIGHT=$((LATEST_HEIGHT - 2000)); \ +TRUST_HASH=$(curl -s "$STATE_SYNC_RPC/block?height=$BLOCK_HEIGHT" | jq -r .result.block_id.hash) + +sed -i.bak -E "s|^(enable[[:space:]]+=[[:space:]]+).*$|\1true| ; \ +s|^(rpc_servers[[:space:]]+=[[:space:]]+).*$|\1\"$STATE_SYNC_RPC,$STATE_SYNC_RPC\"| ; \ +s|^(trust_height[[:space:]]+=[[:space:]]+).*$|\1$BLOCK_HEIGHT| ; \ +s|^(trust_hash[[:space:]]+=[[:space:]]+).*$|\1\"$TRUST_HASH\"|" {{ ansible_env.HOME }}/{{ cosmos_folder }}/config/config.toml diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..00cd770 --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup, find_packages + +setup( + name='crun', + version='0.1', + packages=find_packages(), + install_requires=[ + 'ansible==6.7.0', + 'ansible-core==2.13.13', + 'ansible-runner==2.3.6', + 'cffi==1.16.0', + 'cryptography==42.0.7', + 'docutils==0.20.1', + 'importlib-metadata==6.2.1', + 'Jinja2==3.1.4', + 'lockfile==0.12.2', + 'MarkupSafe==2.1.5', + 'packaging==24.0', + 'pexpect==4.9.0', + 'ptyprocess==0.7.0', + 'pycparser==2.22', + 'python-daemon==3.0.1', + 'PyYAML==6.0.1', + 'resolvelib==0.8.1', + 'six==1.16.0', + 'zipp==3.18.1', + 'click==8.1.7', + 'Pygments==2.18.0' + + ], +) +