diff --git a/.gitignore b/.gitignore index 2bfa8cf5b7..4944f23252 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ config-files/*.last_processed_batch.json nonce_*.bin infra/ansible/playbooks/ini/**.ini +infra/ansible/playbooks/files/**.pem diff --git a/Makefile b/Makefile index 438673a53d..2042d6ad50 100644 --- a/Makefile +++ b/Makefile @@ -1268,6 +1268,10 @@ ansible_operator_deploy: ## Deploy the Operator. Parameters: INVENTORY -e "ecdsa_keystore_path=$(ECDSA_KEYSTORE)" \ -e "bls_keystore_path=$(BLS_KEYSTORE)" +ansible_explorer_deploy: + @ansible-playbook infra/ansible/playbooks/explorer.yaml \ + -i $(INVENTORY) + ansible_telemetry_create_env: @cp -n infra/ansible/playbooks/ini/config-telemetry.ini.example infra/ansible/playbooks/ini/config-telemetry.ini @echo "Config files for Telemetry created in infra/ansible/playbooks/ini" diff --git a/explorer/.gitignore b/explorer/.gitignore index 4b3fc91a10..f7c442ef2c 100644 --- a/explorer/.gitignore +++ b/explorer/.gitignore @@ -33,7 +33,7 @@ explorer-*.tar # In case you use Node.js/npm, you want to ignore these. npm-debug.log -/assets/node_modules/ +assets/node_modules/ # Environment Variables /.env diff --git a/explorer/lib/explorer/release.ex b/explorer/lib/explorer/release.ex new file mode 100644 index 0000000000..e94507410b --- /dev/null +++ b/explorer/lib/explorer/release.ex @@ -0,0 +1,28 @@ +defmodule Explorer.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :explorer + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + Application.load(@app) + end +end diff --git a/explorer/rel/overlays/bin/migrate b/explorer/rel/overlays/bin/migrate new file mode 100755 index 0000000000..5ef1ffa015 --- /dev/null +++ b/explorer/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./explorer eval Explorer.Release.migrate diff --git a/explorer/rel/overlays/bin/migrate.bat b/explorer/rel/overlays/bin/migrate.bat new file mode 100755 index 0000000000..01fc226284 --- /dev/null +++ b/explorer/rel/overlays/bin/migrate.bat @@ -0,0 +1 @@ +call "%~dp0\explorer" eval Explorer.Release.migrate diff --git a/explorer/rel/overlays/bin/server b/explorer/rel/overlays/bin/server new file mode 100755 index 0000000000..195d200183 --- /dev/null +++ b/explorer/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./explorer start diff --git a/explorer/rel/overlays/bin/server.bat b/explorer/rel/overlays/bin/server.bat new file mode 100755 index 0000000000..9bde81e193 --- /dev/null +++ b/explorer/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\explorer" start diff --git a/infra/ansible/playbooks/elixir.yaml b/infra/ansible/playbooks/elixir.yaml index dc72755511..5cc18cda53 100644 --- a/infra/ansible/playbooks/elixir.yaml +++ b/infra/ansible/playbooks/elixir.yaml @@ -49,6 +49,7 @@ deb: "{{ download_libssl.dest }}" when: libssl_check.rc != 0 + ########## Install Erlang 26.2.1-1 ########## - name: Check if Erlang 26.2.1-1 is installed become: true ansible.builtin.shell: @@ -57,6 +58,7 @@ changed_when: false failed_when: erlang_check.rc not in [0, 1] + - name: Download Erlang 26.2.1-1 become: true register: download_erlang diff --git a/infra/ansible/playbooks/explorer.yaml b/infra/ansible/playbooks/explorer.yaml index 02d3991587..dec8cb265b 100644 --- a/infra/ansible/playbooks/explorer.yaml +++ b/infra/ansible/playbooks/explorer.yaml @@ -1,43 +1,135 @@ -- import_playbook: setup.yaml -- import_playbook: webserver.yaml -#- import_playbook: elixir.yaml # Not working -#- import_playbook: docker.yaml +- name: Run setup playbook + ansible.builtin.import_playbook: setup.yaml + vars: + host: explorer + +- name: Run elixir playbook + ansible.builtin.import_playbook: elixir.yaml + vars: + host: explorer + +- name: Run nodejs playbook + ansible.builtin.import_playbook: nodejs.yaml + vars: + host: explorer -- hosts: aligned-holesky-explorer - become: true +- name: Run postgres playbook + ansible.builtin.import_playbook: postgres.yaml vars: - user: "{{ user }}" + host: explorer + ini_file: ini/config-explorer.ini + +- name: Setup Explorer + hosts: explorer + vars: + service: "explorer" + + pre_tasks: + - name: Install pnpm + become: true + ansible.builtin.shell: + cmd: npm install -g pnpm + vars: + ansible_ssh_user: "{{ admin_user }}" + + - name: Allow all access to tcp port 443 + become: true + ufw: + rule: allow + port: 443 + proto: tcp + vars: + ansible_ssh_user: "{{ admin_user }}" + + - name: Clone the aligned_layer repository + ansible.builtin.git: + repo: https://github.com/yetanotherco/aligned_layer + dest: "/home/{{ ansible_user }}/repos/explorer/aligned_layer" + update: yes + + - name: Create .ssl directory + file: + path: /home/{{ ansible_user }}/.ssl/ + state: directory + + - name: Upload SSL key to server (infra/ansible/playbooks/files/key.pem) + copy: + src: key.pem + dest: /home/{{ ansible_user }}/.ssl/key.pem + + - name: Upload SSL certificate to server (infra/ansible/playbooks/files/cert.pem) + copy: + src: cert.pem + dest: /home/{{ ansible_user }}/.ssl/cert.pem tasks: - # Install required packages - - name: Update apt and install required system packages - apt: - pkg: - - unzip - state: latest - update_cache: true - - # Create directories for each service - - name: Create directories for each service if do not exist - ansible.builtin.file: - path: /home/{{ user }}/repos/{{ item }} + - name: Add environment file for Explorer + template: + src: explorer/explorer_env.j2 + dest: /home/{{ ansible_user }}/repos/explorer/aligned_layer/explorer/.env + vars: + MIX_ENV: prod + RPC_URL: "{{ lookup('ini', 'RPC_URL file=ini/config-explorer.ini') }}" + ENVIRONMENT: "{{ lookup('ini', 'ENVIRONMENT file=ini/config-explorer.ini') }}" + ALIGNED_CONFIG_FILE: "{{ lookup('ini', 'ALIGNED_CONFIG_FILE file=ini/config-explorer.ini') }}" + PHX_HOST: "{{ lookup('ini', 'PHX_HOST file=ini/config-explorer.ini') }}" + PHX_SERVER: true + ELIXIR_HOSTNAME: "{{ lookup('ini', 'ELIXIR_HOSTNAME file=ini/config-explorer.ini') }}" + DB_NAME: "{{ lookup('ini', 'DB_NAME file=ini/config-explorer.ini') }}" + DB_USER: "{{ lookup('ini', 'DB_USER file=ini/config-explorer.ini') }}" + DB_PASS: "{{ lookup('ini', 'DB_PASS file=ini/config-explorer.ini') }}" + DB_HOST: "{{ lookup('ini', 'DB_HOST file=ini/config-explorer.ini') }}" + TRACKER_API_URL: "{{ lookup('ini', 'TRACKER_API_URL file=ini/config-explorer.ini') }}" + SECRET_KEY_BASE: "{{ lookup('ini', 'SECRET_KEY_BASE file=ini/config-explorer.ini') }}" + KEYFILE_PATH: "{{ lookup('ini', 'KEYFILE_PATH file=ini/config-explorer.ini') }}" + CERTFILE_PATH: "{{ lookup('ini', 'CERTFILE_PATH file=ini/config-explorer.ini') }}" + BATCH_TTL_MINUTES: "{{ lookup('ini', 'BATCH_TTL_MINUTES file=ini/config-explorer.ini') }}" + SCHEDULED_BATCH_INTERVAL_MINUTES: "{{ lookup('ini', 'SCHEDULED_BATCH_INTERVAL_MINUTES file=ini/config-explorer.ini') }}" + LATEST_RELEASE: "{{ lookup('ini', 'LATEST_RELEASE file=ini/config-explorer.ini') }}" + + - name: Build the explorer release + args: + chdir: "/home/{{ ansible_user }}/repos/explorer/aligned_layer/explorer" + environment: + MIX_ENV: prod + shell: + executable: /bin/bash + cmd: | + set -ex + source .env + mix local.hex --force + mix local.rebar --force + mix deps.get --only $MIX_ENV + mix compile + pnpm --prefix=assets/ install + mix phx.digest + mix assets.deploy + mix release --overwrite + mix ecto.migrate + + - name: Set CAP_NET_BIND_SERVICE to beam + shell: + cmd: sudo setcap CAP_NET_BIND_SERVICE=+eip /home/app/repos/explorer/aligned_layer/explorer/_build/prod/rel/explorer/erts-14.2.1/bin/beam.smp + vars: + ansible_ssh_user: "{{ admin_user }}" + + - name: Create .env for Explorer systemd service + shell: cat /home/{{ ansible_user }}/repos/explorer/aligned_layer/explorer/.env | sed 's/export //g' > /home/{{ ansible_user }}/config/.env.explorer + + - name: Create systemd services directory + file: + path: "/home/{{ ansible_user }}/.config/systemd/user/" state: directory - mode: '0755' - become_user: "{{ user }}" - loop: - - explorer - # Clone Aligned repository for each service - - name: Clone Aligned repository - ansible.builtin.git: - repo: https://github.com/yetanotherco/aligned_layer.git - dest: /home/{{ user }}/repos/{{ item }}/aligned_layer - version: v0.10.2 - become_user: "{{ user }}" - loop: - - explorer - register: repo_clone - failed_when: - - repo_clone.failed - - not 'Local modifications exist in the destination' in repo_clone.msg + - name: Add service to systemd + template: + src: services/explorer.service.j2 + dest: "/home/{{ ansible_user }}/.config/systemd/user/explorer.service" + force: no + - name: Start explorer service + systemd_service: + name: explorer + state: started + enabled: true + scope: user diff --git a/infra/ansible/playbooks/ini/config-explorer.ini.example b/infra/ansible/playbooks/ini/config-explorer.ini.example new file mode 100644 index 0000000000..2d2d3a1f2c --- /dev/null +++ b/infra/ansible/playbooks/ini/config-explorer.ini.example @@ -0,0 +1,17 @@ +[global] +RPC_URL= +ENVIRONMENT= +ALIGNED_CONFIG_FILE= +PHX_HOST= +ELIXIR_HOSTNAME= +DB_NAME= +DB_USER= +DB_PASS= +DB_HOST= +TRACKER_API_URL= +SECRET_KEY_BASE= +KEYFILE_PATH= +CERTFILE_PATH= +BATCH_TTL_MINUTES= +SCHEDULED_BATCH_INTERVAL_MINUTES= +LATEST_RELEASE= diff --git a/infra/ansible/playbooks/nodejs.yaml b/infra/ansible/playbooks/nodejs.yaml new file mode 100644 index 0000000000..352d65ac7f --- /dev/null +++ b/infra/ansible/playbooks/nodejs.yaml @@ -0,0 +1,35 @@ +- name: Nodejs Setup + hosts: "{{ host }}" + + vars: + ansible_ssh_user: "{{ admin_user }}" + node_version: "22.11.0" + node_archive: "node-v{{ node_version }}-linux-x64.tar.xz" + node_url: "https://nodejs.org/dist/v{{ node_version }}/{{ node_archive }}" + node_install_dir: "/usr/local/" + node_bin_path: "/usr/local/bin/node" + + tasks: + - name: Check if Node.js is already installed + ansible.builtin.shell: + cmd: "which {{ node_bin_path }}" + register: node_check + changed_when: false + failed_when: node_check.rc not in [0, 1] + + - name: Download Node.js v{{ node_version }} + become: true + register: download_nodejs + ansible.builtin.get_url: + url: "{{ node_url }}" + dest: "/root/{{ node_archive }}" + mode: '0644' + when: node_check.rc != 0 + + - name: Install Node.js v{{ node_version }} + become: true + ansible.builtin.shell: + cmd: "tar --strip-components=1 --directory={{ node_install_dir }} -xf {{ download_nodejs.dest }}" + args: + creates: "{{ node_bin_path }}" + when: node_check.rc != 0 diff --git a/infra/ansible/playbooks/postgres.yaml b/infra/ansible/playbooks/postgres.yaml index 2b00e894cc..300cf55482 100644 --- a/infra/ansible/playbooks/postgres.yaml +++ b/infra/ansible/playbooks/postgres.yaml @@ -21,7 +21,7 @@ path: /usr/share/postgresql-common/pgdg state: directory mode: '0755' - + - name: Download postgres ca-certificates if not already present become: true ansible.builtin.get_url: @@ -49,10 +49,11 @@ - name: Create PostgreSQL credentials shell: cmd: | - sudo -u postgres psql -U postgres -c "CREATE USER {{ postgresql_telemetry_user }} WITH PASSWORD '{{ postgresql_telemetry_pass }}';" - sudo -u postgres psql -U postgres -c "CREATE DATABASE {{ postgresql_telemetry_db_name }} OWNER {{ postgresql_telemetry_user }};" + sudo -u postgres psql -U postgres -c "CREATE USER {{ DB_USER }} WITH PASSWORD '{{ DB_PASS }}';" + sudo -u postgres psql -U postgres -c "CREATE DATABASE {{ DB_NAME }} OWNER {{ DB_USER }};" vars: - postgresql_telemetry_user: "{{ lookup('ini', 'postgresql_telemetry_user', file=ini_file) }}" - postgresql_telemetry_pass: "{{ lookup('ini', 'postgresql_telemetry_pass', file=ini_file) }}" - postgresql_telemetry_db_name: "{{ lookup('ini', 'postgresql_telemetry_db_name', file=ini_file) }}" + DB_USER: "{{ lookup('ini', 'DB_USER', file=ini_file) }}" + DB_PASS: "{{ lookup('ini', 'DB_PASS', file=ini_file) }}" + DB_NAME: "{{ lookup('ini', 'DB_NAME', file=ini_file) }}" ignore_errors: true + no_log: true diff --git a/infra/ansible/playbooks/setup.yaml b/infra/ansible/playbooks/setup.yaml index b78e43f379..5d431389c2 100644 --- a/infra/ansible/playbooks/setup.yaml +++ b/infra/ansible/playbooks/setup.yaml @@ -1,4 +1,5 @@ -- hosts: "{{ host }}" +- name: Server setup + hosts: "{{ host }}" tasks: # Install required packages diff --git a/infra/ansible/playbooks/templates/explorer/explorer_env.j2 b/infra/ansible/playbooks/templates/explorer/explorer_env.j2 new file mode 100644 index 0000000000..27ab0fb36a --- /dev/null +++ b/infra/ansible/playbooks/templates/explorer/explorer_env.j2 @@ -0,0 +1,20 @@ +export RPC_URL={{ RPC_URL }} +export ENVIRONMENT={{ ENVIRONMENT }} +export ALIGNED_CONFIG_FILE={{ ALIGNED_CONFIG_FILE }} +export PHX_HOST={{ PHX_HOST }} +export ELIXIR_HOSTNAME={{ ELIXIR_HOSTNAME }} +export PHX_SERVER=true + +export DB_NAME={{ DB_NAME }} +export DB_USER={{ DB_USER }} +export DB_PASS={{ DB_PASS }} +export DB_HOST={{ DB_HOST }} + +export TRACKER_API_URL={{ TRACKER_API_URL }} +export SECRET_KEY_BASE={{ SECRET_KEY_BASE }} + +export KEYFILE_PATH={{ KEYFILE_PATH }} +export CERTFILE_PATH={{ CERTFILE_PATH }} + +export BATCH_TTL_MINUTES={{ BATCH_TTL_MINUTES }} +export SCHEDULED_BATCH_INTERVAL_MINUTES={{ SCHEDULED_BATCH_INTERVAL_MINUTES }} diff --git a/infra/ansible/playbooks/templates/services/explorer.service.j2 b/infra/ansible/playbooks/templates/services/explorer.service.j2 new file mode 100644 index 0000000000..a4959d84f9 --- /dev/null +++ b/infra/ansible/playbooks/templates/services/explorer.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=Explorer +After=network.target + +[Service] +Type=simple +WorkingDirectory=/home/{{ ansible_user }}/repos/explorer/aligned_layer/explorer +EnvironmentFile=/home/{{ ansible_user }}/config/.env.explorer +ExecStart=/home/{{ ansible_user }}/repos/explorer/aligned_layer/explorer/_build/prod/rel/explorer/bin/explorer start +Restart=always +RestartSec=1 +StartLimitBurst=100 +LimitNOFILE=100000 + +[Install] +WantedBy=multi-user.target diff --git a/infra/ansible/stage_inventory.yaml b/infra/ansible/stage_inventory.yaml index 681117cd0a..ad3dedb3d9 100644 --- a/infra/ansible/stage_inventory.yaml +++ b/infra/ansible/stage_inventory.yaml @@ -1,14 +1,21 @@ aggregator: hosts: - aligned-holesky-aggregator-1: - ansible_host: aligned-holesky-stage-1-aggregator + aligned-holesky-stage-2-aggregator: + ansible_host: aligned-holesky-stage-2-aggregator admin_user: admin ansible_user: app ansible_python_interpreter: /usr/bin/python3 batcher: hosts: - aligned-holesky-stage-1: - ansible_host: aligned-holesky-stage-1 + aligned-holesky-stage-2-batcher: + ansible_host: aligned-holesky-stage-2-batcher + admin_user: admin + ansible_user: app + ansible_python_interpreter: /usr/bin/python3 +explorer: + hosts: + aligned-holesky-stage-2-explorer: + ansible_host: aligned-holesky-stage-2-explorer admin_user: admin ansible_user: app ansible_python_interpreter: /usr/bin/python3 @@ -19,12 +26,6 @@ telemetry: admin_user: admin ansible_user: app ansible_python_interpreter: /usr/bin/python3 -# explorer: -# hosts: -# aligned-holesky-explorer: -# ansible_host: aligned-holesky-explorer -# ansible_user: admin -# ansible_python_interpreter: /usr/bin/python3 operator: hosts: operator-1: