diff --git a/README.md b/README.md index 2e591e1..5a0c70d 100755 --- a/README.md +++ b/README.md @@ -27,6 +27,24 @@ SLV is a toolkit for Solana developers. It provides a set of tools to help developers build, test, and deploy Solana Validatros and Solana-based applications. +In the newly revamped slv, you can complete all configurations from a remote +machine—no more direct node logins required. This approach ensures that only +essential packages are installed on the node, leaving behind no unnecessary +clutter. We’ve also introduced Ansible Playbooks & Jinja Template for Linux +configuration, allowing us to manage and migrate multiple validators with +greater efficiency and security. + +Additionally, the new slv always uses a dummy key (an invalid key named +“unstaked-identity.json”) for its initial startup. Once you confirm everything +is running smoothly, you simply set the actual Identity and switch to the active +key. Following this consistent flow helps prevent double votes and ensures +you’re prepared in case the node ever becomes unreachable. + +We’ll continue to provide method documentation along the way, and we look +forward to your ongoing support! + +[Validators DAO Discord](https://discord.gg/X4BgkBHavp) + ## Dependencies - OS MacOS or Linux @@ -40,12 +58,20 @@ slv Install script will install the following dependencies. Please install the following dependencies manually if install script fails. -## Installation +## Installation & Validator Launch Install slv CLI ```bash curl -fsSL https://storage.slv.dev/slv/install | sh +slv validator init +slv validator deploy +``` + +## Usage + +```bash +slv v --help ``` ## Deploy Solana Validator Testnet with Firedancer @@ -57,7 +83,7 @@ This command will prompt you to provide necessary information to deploy. New slv Deployment is always use `unstaked-keypair.json` for the identity key. This is the best practice to avoid double voting, and etc. -So Please make sure to set the aurhorized identity key with `identity.json` +So Please make sure to set the aurhorized identity key with `slv v set:identity` after the deployment. ### Input Server's Default Username @@ -161,13 +187,20 @@ from the vote account. ```bash ? Please Enter Your Vote Account's Authrority Key › ✔︎ Validator testnet config saved to /Users/fumi/.slv/config.validator.testnet.yml + +Now you can deploy with: + +$ slv v deploy -n testnet ``` -### Confirm the Configuration and Deploy +Now your configuration is saved to `~/.slv/config.validator.testnet.yml`. + +### Deploy the Solana Validator Once you confirm the configuration, the deployment will start. ```bash +slv v deploy -n testnet Your Testnet Validators Settings: ┌────────────────┬──────────────────────────────────────────────┐ │ Identity Key │ EjDwu2Czy8eWEYRuNwtjniYks47Du3KNJ6JY9rs3aFSV │ @@ -182,15 +215,7 @@ Your Testnet Validators Settings: ├────────────────┼──────────────────────────────────────────────┤ │ Version │ 0.302.20104 │ └────────────────┴──────────────────────────────────────────────┘ -Now you can deploy with: - -$ slv v deploy -n testnet -``` - -All set! You can deploy the Solana Validator with the following command. - -```bash -slv v deploy -n testnet +? Do you want to continue? (Y/n) › Yes ``` It's done! Your Solana Validator is now deployed. It will take some time to @@ -229,6 +254,30 @@ validator. slv v restart -n testnet --pubkey --rm ``` +### slv Validator Commands + +```bash +Usage: slv validator +Version: 0.3.1 + +Description: + + Manage Solana Validator Nodes + +Options: + + -h, --help - Show this help. + +Commands: + + init - Initialize a new validator + deploy - Deploy Validators + list - List validators + set:identity - Set Validator Identity + set:unstaked - Set Validator Identity to Unstaked Key Stop/Change Identity/Start + restart - Restart validator +``` + ### Community Support If you have any questions or need help, please join our Discord community. @@ -243,8 +292,6 @@ If you have any questions or need help, please join our Discord community. progress) - [] Add `slv validator setup --shredstream` for ShredStream Node Build (in progress) -- [] add `slv validator migrate` for Solana Validator Migration from `solv4` (in - progress) - [] Add CI/CD pipeline (Github Actions) for `slv` Release (in progress) - [] Add `slv bot` for gRPC Geyser Client (in progress) - [] Add `slv swap` for Solana Token Swap (in progress) diff --git a/cli/deno.json b/cli/deno.json index a4f8d0e..824bb92 100644 --- a/cli/deno.json +++ b/cli/deno.json @@ -1,6 +1,6 @@ { "name": "@slv/cli", - "version": "0.2.0", + "version": "0.3.1", "exports": "./dist/exe", "publish": { "include": ["src"], diff --git a/deno.json b/deno.json index e79aa6a..c93512b 100644 --- a/deno.json +++ b/deno.json @@ -5,13 +5,14 @@ "test": "deno test -A", "s": "deno run -A cli/src/index.ts", "dev": "deno run -A --watch cli/src/index.ts", + "build:all": "deno task build && deno task upload:script && deno task upload:exe && deno task upload:template", "build": "deno task build:linux & deno task build:mac", "build:linux": "deno compile -A --target x86_64-unknown-linux-gnu --no-check --output dist/slv-x86_64-unknown-linux-gnu-exe cli/src/index.ts && tar -czvf dist/slv-x86_64-unknown-linux-gnu-exe.tar.gz dist/slv-x86_64-unknown-linux-gnu-exe", "build:mac": "deno compile -A --target x86_64-apple-darwin --no-check --output dist/slv-x86_64-apple-darwin-exe cli/src/index.ts && tar -czvf dist/slv-x86_64-apple-darwin-exe.tar.gz dist/slv-x86_64-apple-darwin-exe", "upload:script": "cd ./sh/ && aws --endpoint-url=https://278a7109e511280594fe6a2ebb778333.r2.cloudflarestorage.com/slv s3 cp install s3://slv/ --content-disposition 'attachment; filename=install'", "upload:exe": "deno run -A cli/uploadExe.ts", - "upload:template": "tar -czf dist/template.tar.gz ./template/0.2.0 && deno run -A cli/uploadTemplate.ts" + "upload:template": "tar -czf dist/template.tar.gz ./template/0.3.1 && deno run -A cli/uploadTemplate.ts" }, "imports": { "@/": "./", diff --git a/sh/0.3.0/install b/sh/0.3.0/install new file mode 100755 index 0000000..1795893 --- /dev/null +++ b/sh/0.3.0/install @@ -0,0 +1,137 @@ +#!/bin/bash + +set -e + +VERSION="0.3.0" +BASE_URL="https://storage.slv.dev/slv" + +detect_platform() { + uname_out="$(uname -s)" + case "${uname_out}" in + Linux*) platform="x86_64-unknown-linux-gnu"; osfamily="linux";; + Darwin*) platform="x86_64-apple-darwin"; osfamily="darwin";; + CYGWIN*|MINGW*|MSYS*|Windows*) platform="x86_64-pc-windows-msvc"; osfamily="windows";; + *) echo "Unsupported platform: ${uname_out}" && exit 1;; + esac +} + +install_slv() { + echo "Detecting platform..." + detect_platform + echo "Platform detected: $platform" + + DOWNLOAD_URL="${BASE_URL}/${VERSION}/${platform}-exe.tar.gz" + TEMPLATE_URL="${BASE_URL}/template/${VERSION}/template.tar.gz" + INSTALL_DIR="/usr/local/bin" + TEMPLATE_DIR="$HOME/.slv/template" + TEMP_DIR=$(mktemp -d) + + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + INSTALL_DIR="$HOME/.local/bin" + mkdir -p "$INSTALL_DIR" + fi + + echo "Temporary directory: $TEMP_DIR" + + echo "Downloading slv from $DOWNLOAD_URL..." + curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_DIR/slv.tar.gz" + + echo "Downloading templates from $TEMPLATE_URL..." + curl -fsSL "$TEMPLATE_URL" -o "$HOME/.slv/template.tar.gz" + + echo "Extracting slv..." + tar -xzvf "$TEMP_DIR/slv.tar.gz" -C "$TEMP_DIR" --strip-components=1 + + echo "Extracting templates..." + tar -xzvf "$HOME/.slv/template.tar.gz" -C "$HOME/.slv" --strip-components=1 + mkdir -p "$HOME/.slv/template" + echo "Copying templates to $HOME/.slv/template" + + SLV_FILE="$TEMP_DIR/slv-x86_64-apple-darwin-exe" + TEMPLATE_DL_DIR="$HOME/.slv/" + + if [ ! -f "$SLV_FILE" ]; then + echo "Error: Extracted file not found." + exit 1 + fi + + echo "Installing slv..." + if [ ! -d "$INSTALL_DIR" ]; then + echo "$INSTALL_DIR does not exist. Creating it..." + sudo mkdir -p "$INSTALL_DIR" + fi + + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + sudo mv "$SLV_FILE" "$INSTALL_DIR/slv.exe" + else + sudo mv "$SLV_FILE" "$INSTALL_DIR/slv" + sudo chmod +x "$INSTALL_DIR/slv" + fi + + echo "Cleaning up..." + rm -rf "$TEMP_DIR" + rm -rf "$TEMP_DIR2" + + echo "slv has been installed successfully!" + echo "Ensure $INSTALL_DIR is in your PATH." + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + echo "Windows users, add $INSTALL_DIR to your PATH manually." + fi + mkdir -p ~/.slv/keys + slv -P +} + +install_dependencies() { + if ! command -v python3 >/dev/null 2>&1; then + echo "Python3 not found. Installing..." + if [ "$osfamily" = "linux" ]; then + # Distribution check + # Ubuntu/Debian + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y python3 python3-pip + # RHEL/CentOS + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y python3 python3-pip + else + echo "No known package manager found. Please install Python3 manually." + exit 1 + fi + elif [ "$osfamily" = "darwin" ]; then + # macOS + if command -v brew >/dev/null 2>&1; then + brew update + brew install python3 + else + echo "Homebrew not found. Please install Homebrew or Python3 manually." + exit 1 + fi + elif [ "$osfamily" = "windows" ]; then + echo "Windows environment detected. Please install Python3 manually (e.g. via choco)." + # Windows coming soon + fi + else + echo "Python3 is already installed." + fi + + if ! command -v pip3 >/dev/null 2>&1; then + echo "pip3 not found. Please ensure python3-pip is installed." + exit 1 + fi + + + if ! command -v ansible >/dev/null 2>&1; then + echo "Ansible not found. Installing via pip3..." + pip3 install --user ansible + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile + source ~/.profile + else + echo "Ansible is already installed." + fi + + echo "Python3 and Ansible installation completed." +} + +install_dependencies +install_slv + diff --git a/sh/0.3.1/install b/sh/0.3.1/install new file mode 100755 index 0000000..a405906 --- /dev/null +++ b/sh/0.3.1/install @@ -0,0 +1,138 @@ +#!/bin/bash + +set -e + +VERSION="0.3.0" +BASE_URL="https://storage.slv.dev/slv" + +detect_platform() { + uname_out="$(uname -s)" + case "${uname_out}" in + Linux*) platform="x86_64-unknown-linux-gnu"; osfamily="linux";; + Darwin*) platform="x86_64-apple-darwin"; osfamily="darwin";; + CYGWIN*|MINGW*|MSYS*|Windows*) platform="x86_64-pc-windows-msvc"; osfamily="windows";; + *) echo "Unsupported platform: ${uname_out}" && exit 1;; + esac +} + +install_slv() { + echo "Detecting platform..." + detect_platform + echo "Platform detected: $platform" + + DOWNLOAD_URL="${BASE_URL}/${VERSION}/${platform}-exe.tar.gz" + TEMPLATE_URL="${BASE_URL}/template/${VERSION}/template.tar.gz" + INSTALL_DIR="/usr/local/bin" + TEMPLATE_DIR="$HOME/.slv/template" + TEMP_DIR=$(mktemp -d) + + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + INSTALL_DIR="$HOME/.local/bin" + mkdir -p "$INSTALL_DIR" + fi + + echo "Temporary directory: $TEMP_DIR" + + echo "Downloading slv from $DOWNLOAD_URL..." + curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_DIR/slv.tar.gz" + + echo "Downloading templates from $TEMPLATE_URL..." + curl -fsSL "$TEMPLATE_URL" -o "$HOME/.slv/template.tar.gz" + + echo "Extracting slv..." + tar -xzvf "$TEMP_DIR/slv.tar.gz" -C "$TEMP_DIR" --strip-components=1 + + echo "Extracting templates..." + tar -xzvf "$HOME/.slv/template.tar.gz" -C "$HOME/.slv" --strip-components=1 + mkdir -p "$HOME/.slv/template" + echo "Copying templates to $HOME/.slv/template" + + SLV_FILE="$TEMP_DIR/slv-x86_64-apple-darwin-exe" + TEMPLATE_DL_DIR="$HOME/.slv/" + + if [ ! -f "$SLV_FILE" ]; then + echo "Error: Extracted file not found." + exit 1 + fi + + echo "Installing slv..." + if [ ! -d "$INSTALL_DIR" ]; then + echo "$INSTALL_DIR does not exist. Creating it..." + sudo mkdir -p "$INSTALL_DIR" + fi + + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + sudo mv "$SLV_FILE" "$INSTALL_DIR/slv.exe" + else + sudo mv "$SLV_FILE" "$INSTALL_DIR/slv" + sudo chmod +x "$INSTALL_DIR/slv" + fi + + echo "Cleaning up..." + rm -rf "$TEMP_DIR" + rm -rf "$TEMP_DIR2" + rm -rf "$HOME/.slv/template.tar.gz" + + echo "slv has been installed successfully!" + echo "Ensure $INSTALL_DIR is in your PATH." + if [ "$platform" == "x86_64-pc-windows-msvc" ]; then + echo "Windows users, add $INSTALL_DIR to your PATH manually." + fi + mkdir -p ~/.slv/keys + slv -P +} + +install_dependencies() { + if ! command -v python3 >/dev/null 2>&1; then + echo "Python3 not found. Installing..." + if [ "$osfamily" = "linux" ]; then + # Distribution check + # Ubuntu/Debian + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y python3 python3-pip + # RHEL/CentOS + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y python3 python3-pip + else + echo "No known package manager found. Please install Python3 manually." + exit 1 + fi + elif [ "$osfamily" = "darwin" ]; then + # macOS + if command -v brew >/dev/null 2>&1; then + brew update + brew install python3 + else + echo "Homebrew not found. Please install Homebrew or Python3 manually." + exit 1 + fi + elif [ "$osfamily" = "windows" ]; then + echo "Windows environment detected. Please install Python3 manually (e.g. via choco)." + # Windows coming soon + fi + else + echo "Python3 is already installed." + fi + + if ! command -v pip3 >/dev/null 2>&1; then + echo "pip3 not found. Please ensure python3-pip is installed." + exit 1 + fi + + + if ! command -v ansible >/dev/null 2>&1; then + echo "Ansible not found. Installing via pip3..." + pip3 install --user ansible + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile + source ~/.profile + else + echo "Ansible is already installed." + fi + + echo "Python3 and Ansible installation completed." +} + +install_dependencies +install_slv + diff --git a/sh/install b/sh/install index cc5a72f..88a0b7a 100644 --- a/sh/install +++ b/sh/install @@ -2,7 +2,7 @@ set -e -VERSION="0.2.0" +VERSION="0.3.1" BASE_URL="https://storage.slv.dev/slv" detect_platform() { @@ -20,8 +20,8 @@ install_slv() { detect_platform echo "Platform detected: $platform" - DOWNLOAD_URL="${BASE_URL}/${VERSION}/${platform}-exe.tar.gz" - TEMPLATE_URL="${BASE_URL}/template/${VERSION}/template.tar.gz" + DOWNLOAD_URL="${BASE_URL}/${VERSION}/${platform}-exe.tar.gz?cachebuster=$(date +%s)" + TEMPLATE_URL="${BASE_URL}/template/${VERSION}/template.tar.gz?cachebuster=$(date +%s)" INSTALL_DIR="/usr/local/bin" TEMPLATE_DIR="$HOME/.slv/template" TEMP_DIR=$(mktemp -d) @@ -71,6 +71,7 @@ install_slv() { echo "Cleaning up..." rm -rf "$TEMP_DIR" rm -rf "$TEMP_DIR2" + rm -rf "$HOME/.slv/template.tar.gz" echo "slv has been installed successfully!" echo "Ensure $INSTALL_DIR is in your PATH." diff --git a/template/0.3.0/ansible/cmn/create_user.yml b/template/0.3.0/ansible/cmn/create_user.yml new file mode 100644 index 0000000..397d403 --- /dev/null +++ b/template/0.3.0/ansible/cmn/create_user.yml @@ -0,0 +1,69 @@ +--- +- name: Create solv user with specific password and configure SSH access + hosts: all + become: yes + vars: + home_paths_authorized_keys: /home/solv/.ssh/authorized_keys + ansible_remote_tmp: /tmp/ansible_tmp + local_public_key_path: "{{ lookup('env', 'HOME') + '/.ssh/id_rsa.pub' }}" + vars_files: + - ~/.slv/config.pwd.yml + tasks: + - name: Ensure solv user exists + user: + name: solv + password: "{{ encrypted_password }}" + state: present + shell: /bin/bash + + - name: Ensure .ssh directory exists for solv user + file: + path: /home/solv/.ssh + state: directory + owner: solv + group: solv + mode: "0700" + + - name: Add local public key to authorized_keys + lineinfile: + path: "{{ home_paths_authorized_keys }}" + line: "{{ lookup('file', local_public_key_path) }}" + create: yes + owner: solv + group: solv + mode: "0600" + + - name: Generate SSH key for solv user if not exists + shell: su - solv -c "ssh-keygen -t rsa -b 4096 -N '' -f /home/solv/.ssh/id_rsa" + args: + creates: /home/solv/.ssh/id_rsa + + - name: Ensure correct permissions for .ssh directory + file: + path: /home/solv/.ssh + state: directory + owner: solv + group: solv + mode: "0700" + + - name: Ensure correct permissions for authorized_keys + file: + path: "{{ home_paths_authorized_keys }}" + state: file + owner: solv + group: solv + mode: "0600" + + - name: Add solv user to sudoers group + user: + name: solv + groups: sudo + append: yes + + - name: Configure sudoers file for solv user (no password required) + lineinfile: + path: /etc/sudoers + state: present + regexp: '^solv ALL=\(ALL\) NOPASSWD:ALL' + line: "solv ALL=(ALL) NOPASSWD:ALL" + validate: "visudo -cf %s" diff --git a/template/0.3.0/ansible/cmn/find_unmounted_disks.sh b/template/0.3.0/ansible/cmn/find_unmounted_disks.sh new file mode 100644 index 0000000..421e13b --- /dev/null +++ b/template/0.3.0/ansible/cmn/find_unmounted_disks.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +find_unmounted_nvme_disks() { + lsblk -nr -o NAME,TYPE,SIZE,MOUNTPOINT | awk ' + $2 == "disk" && + $1 ~ /^nvme/ && + (($3 ~ /G$/ && substr($3, 1, length($3)-1) + 0 >= 800) || + ($3 ~ /T$/ && substr($3, 1, length($3)-1) + 0 >= 0.8)) && + ($4 == "" || $4 ~ /^[[:space:]]*$/) && + system("lsblk -nr -o TYPE /dev/" $1 " | grep -q part") != 0 {print $1}' +} + +find_unmounted_nvme_disks diff --git a/template/0.3.0/ansible/cmn/install_agave.yml b/template/0.3.0/ansible/cmn/install_agave.yml new file mode 100644 index 0000000..2e40793 --- /dev/null +++ b/template/0.3.0/ansible/cmn/install_agave.yml @@ -0,0 +1,33 @@ +--- +- name: Install Agave + hosts: all + become: true + vars_files: + - ~/.slv/config.validator.testnet.yml + vars: + solana_bin_dir: '/home/solv/.local/share/solana/install/active_release/bin' + tasks: + - name: Select node config by identity_account + set_fact: + selected_validator: '{{ item }}' + loop: '{{ validators }}' + when: item.identity_account == hostvars[inventory_hostname].identity_account + + - name: Install Agave from the specified version + shell: | + tag=v{{ selected_validator.solana_version }} + curl -sSfL https://release.anza.xyz/${tag}/install | sh + args: + executable: /bin/bash + environment: + PATH: '{{ solana_bin_dir }}:{{ ansible_env.PATH }}' + HOME: '/home/solv' + + - name: Verify Solana installation + shell: '{{ solana_bin_dir }}/solana --version' + register: solana_version_check + failed_when: solana_version_check.rc != 0 + + - name: Debug Solana version check + debug: + var: solana_version_check.stdout diff --git a/template/0.3.0/ansible/cmn/install_package.yml b/template/0.3.0/ansible/cmn/install_package.yml new file mode 100644 index 0000000..d9c6348 --- /dev/null +++ b/template/0.3.0/ansible/cmn/install_package.yml @@ -0,0 +1,43 @@ +--- +- name: Install required packages + hosts: all + become: yes + tasks: + - name: Fix broken packages (optional) + become: yes + command: apt-get dist-upgrade -y + - name: Update apt cache + apt: + update_cache: yes + tags: setup_lib + + - name: Install fail2ban + apt: + name: fail2ban + state: present + tags: setup_lib + + - name: Ensure fail2ban is started and enabled + service: + name: fail2ban + state: started + enabled: yes + tags: setup_lib + + - name: Install development tools and libraries + apt: + name: + - libsasl2-dev + - build-essential + - libssl-dev + - libudev-dev + - pkg-config + - zlib1g-dev + - llvm + - clang + - cmake + - make + - libprotobuf-dev + - protobuf-compiler + state: present + tags: setup_lib diff --git a/template/0.3.0/ansible/cmn/install_rust.yml b/template/0.3.0/ansible/cmn/install_rust.yml new file mode 100644 index 0000000..383cd0b --- /dev/null +++ b/template/0.3.0/ansible/cmn/install_rust.yml @@ -0,0 +1,57 @@ +--- +- name: Install Rust and Cargo + hosts: all + become: true + vars: + rust_url: 'https://sh.rustup.rs' + tasks: + - name: Ensure Rust installer is downloaded + get_url: + url: '{{ rust_url }}' + dest: /tmp/rustup-install.sh + mode: 0755 + + - name: Install Rust + shell: | + sudo -u solv /tmp/rustup-install.sh -y + args: + chdir: /home/solv/ + register: install_output + + - name: Debug Rust installation output + debug: + var: install_output + + - name: Add Cargo bin directory to PATH manually + shell: | + echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> /home/solv/.bashrc + args: + executable: /bin/bash + + - name: Source .bashrc to ensure PATH is updated + shell: | + sudo -i -u solv bash -c "source ~/.bashrc" + args: + executable: /bin/bash + + - name: Install rustfmt component + shell: | + sudo -i -u solv bash -c "rustup component add rustfmt" + environment: + PATH: /bin:/usr/bin:/usr/local/bin:/home/solv/.cargo/bin + register: rustup_output + + - name: Debug rustfmt installation output + debug: + var: rustup_output + + - name: Verify Rust installation + shell: | + sudo -i -u solv bash -c "cargo --version" + environment: + PATH: /bin:/usr/bin:/usr/local/bin:/home/solv/.cargo/bin + register: cargo_version + + - name: Debug Rust version + debug: + var: cargo_version diff --git a/template/0.3.0/ansible/cmn/mount_disks.yml b/template/0.3.0/ansible/cmn/mount_disks.yml new file mode 100644 index 0000000..7f1175e --- /dev/null +++ b/template/0.3.0/ansible/cmn/mount_disks.yml @@ -0,0 +1,107 @@ +--- +- name: Mount and configure disks with NVMe prioritization + hosts: all + become: true + vars: + mount_dirs: + - /mnt + - /mnt/ledger + - /mnt/accounts + - /mnt/snapshot + tasks: + - name: Ensure swap is disabled + block: + - name: Check for active swap + shell: swapon --show --noheadings --output NAME | awk '{print $1}' + register: active_swap + changed_when: false + + - name: Debug active swap devices + debug: + var: active_swap.stdout_lines + + - name: Disable active swap devices + command: swapoff {{ item }} + loop: "{{ active_swap.stdout_lines }}" + when: active_swap.stdout_lines | length > 0 + + - name: Remove swap entries from /etc/fstab + replace: + path: /etc/fstab + regexp: ".*swap.*" + replace: "" + when: active_swap.stdout_lines | length > 0 + + - name: Ensure /mnt and subdirectories exist with correct ownership + file: + path: "{{ item }}" + state: directory + owner: solv + group: solv + mode: "0755" + loop: "{{ mount_dirs }}" + + - name: Execute shell script to find unmounted disks + shell: | + lsblk -nr -o NAME,TYPE,SIZE,MOUNTPOINT | awk ' + $2 == "disk" && + $1 ~ /^nvme/ && + (($3 ~ /G$/ && substr($3, 1, length($3)-1) + 0 >= 800) || + ($3 ~ /T$/ && substr($3, 1, length($3)-1) + 0 >= 0.8)) && + ($4 == "" || $4 ~ /^[[:space:]]*$/) && + system("lsblk -nr -o TYPE /dev/" $1 " | grep -q part") != 0 {print $1}' + register: unmounted_disks_output + args: + chdir: /home/solv + + - name: Debug unmounted disks + debug: + var: unmounted_disks_output.stdout_lines + + - name: Remove the find_unmounted_disks.sh script + file: + path: /home/solv/find_unmounted_disks.sh + state: absent + + - name: Skip if no unmounted disks found + debug: + msg: "No unmounted disks found, skipping disk formatting and mounting tasks." + when: unmounted_disks_output.stdout_lines | length == 0 + + - name: Mount and format disks dynamically + block: + - name: Format disk to ext4 if not already formatted + filesystem: + fstype: ext4 + dev: "/dev/{{ item }}" + loop: "{{ unmounted_disks_output.stdout_lines }}" + loop_control: + label: "{{ item }}" + + - name: Mount /mnt/ledger + mount: + path: /mnt/ledger + src: "/dev/{{ unmounted_disks_output.stdout_lines[0] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 1 + + - name: Mount /mnt/accounts + mount: + path: /mnt/accounts + src: "/dev/{{ unmounted_disks_output.stdout_lines[1] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 2 + + - name: Mount /mnt/snapshot + mount: + path: /mnt/snapshot + src: "/dev/{{ unmounted_disks_output.stdout_lines[2] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 3 + when: unmounted_disks_output.stdout_lines | length > 0 diff --git a/template/0.3.0/ansible/cmn/optimize_system.yml b/template/0.3.0/ansible/cmn/optimize_system.yml new file mode 100644 index 0000000..0e3ee21 --- /dev/null +++ b/template/0.3.0/ansible/cmn/optimize_system.yml @@ -0,0 +1,64 @@ +--- +- name: Optimize system performance for Solana + hosts: all + become: true + tasks: + - name: Apply sysctl performance settings + blockinfile: + path: /etc/sysctl.conf + block: | + # TCP Buffer Sizes (10k min, 87.38k default, 12M max) + net.ipv4.tcp_rmem=10240 87380 12582912 + net.ipv4.tcp_wmem=10240 87380 12582912 + + # TCP Optimization + net.ipv4.tcp_congestion_control=westwood + net.ipv4.tcp_fastopen=3 + net.ipv4.tcp_timestamps=0 + net.ipv4.tcp_sack=1 + net.ipv4.tcp_low_latency=1 + net.ipv4.tcp_tw_reuse=1 + net.ipv4.tcp_no_metrics_save=1 + net.ipv4.tcp_moderate_rcvbuf=1 + + # Kernel Optimization + kernel.timer_migration=0 + kernel.hung_task_timeout_secs=30 + kernel.pid_max=49152 + + # Virtual Memory Tuning + vm.swappiness=30 + vm.max_map_count=2000000 + vm.stat_interval=10 + vm.dirty_ratio=40 + vm.dirty_background_ratio=10 + vm.min_free_kbytes=3000000 + vm.dirty_expire_centisecs=36000 + vm.dirty_writeback_centisecs=3000 + vm.dirtytime_expire_seconds=43200 + + # Solana Specific Tuning + net.core.rmem_max=134217728 + net.core.rmem_default=134217728 + net.core.wmem_max=134217728 + net.core.wmem_default=134217728 + marker: '# {mark} SOLANA SETTINGS' + notify: Reload sysctl + + - name: Install cpufrequtils for CPU governor management + apt: + name: cpufrequtils + state: present + + - name: Set CPU governor to performance in config file + lineinfile: + path: /etc/default/cpufrequtils + line: 'GOVERNOR="performance"' + create: yes + + - name: Set CPU governor to performance for all CPUs + shell: echo "performance" > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor || true + + handlers: + - name: Reload sysctl + command: sysctl -p diff --git a/template/0.3.0/ansible/cmn/setup-logrotate.yml b/template/0.3.0/ansible/cmn/setup-logrotate.yml new file mode 100644 index 0000000..050f7c9 --- /dev/null +++ b/template/0.3.0/ansible/cmn/setup-logrotate.yml @@ -0,0 +1,43 @@ +--- +- name: Configure log rotation for Solana validator + hosts: all + become: true + tasks: + - name: Ensure logrotate configuration directory exists + file: + path: /etc/logrotate.d + state: directory + mode: '0755' + owner: root + group: root + + - name: Create logrotate configuration for Solana validator + copy: + dest: /etc/logrotate.d/solana + content: | + /home/solv/solana-validator.log { + su solv solv + daily + rotate 1 + size 4G + missingok + compress + postrotate + systemctl kill -s USR1 solv.service + endscript + } + owner: root + group: root + mode: '0644' + + - name: Test logrotate configuration + command: logrotate --debug /etc/logrotate.conf + register: logrotate_debug + failed_when: "'error' in logrotate_debug.stderr" + + - name: Debug logrotate test output + debug: + var: logrotate_debug.stdout + + - name: Force logrotate to apply the new configuration + command: logrotate -f /etc/logrotate.conf diff --git a/template/0.3.0/ansible/cmn/setup-unstaked-identity.yml b/template/0.3.0/ansible/cmn/setup-unstaked-identity.yml new file mode 100644 index 0000000..574315b --- /dev/null +++ b/template/0.3.0/ansible/cmn/setup-unstaked-identity.yml @@ -0,0 +1,19 @@ +--- +- name: Create and link Solana identity files + hosts: all + become: true + tasks: + - name: Create unstaked-identity.json using solana-keygen + shell: | + sudo -u solv bash -c 'source ~/.profile && solana-keygen new --outfile /home/solv/unstaked-identity.json --no-passphrase' + args: + creates: /home/solv/unstaked-identity.json # Skip if file already exists + + - name: Ensure /home/solv/identity.json is a symlink to unstaked-identity.json + file: + src: /home/solv/unstaked-identity.json + dest: /home/solv/identity.json + state: link + + - name: Update ownership of identity.json symlink + command: chown --no-dereference solv:solv /home/solv/identity.json diff --git a/template/0.3.0/ansible/cmn/setup_norestart.yml b/template/0.3.0/ansible/cmn/setup_norestart.yml new file mode 100644 index 0000000..53ae99b --- /dev/null +++ b/template/0.3.0/ansible/cmn/setup_norestart.yml @@ -0,0 +1,26 @@ +--- +- name: Configure needrestart for solv.service + hosts: all + become: true + tasks: + - name: Ensure needrestart configuration directory exists + file: + path: /etc/needrestart/conf.d + state: directory + mode: '0755' + + - name: Configure needrestart to exclude solv.service + copy: + content: '$nrconf{override_rc}{qr(^solv\.service$)} = 0;' + dest: /etc/needrestart/conf.d/solv.conf + owner: root + group: root + mode: '0644' + + - name: Configure needrestart to exclude firedancer.service + copy: + content: '$nrconf{override_rc}{qr(^firedancer\.service$)} = 0;' + dest: /etc/needrestart/conf.d/firedancer.conf + owner: root + group: root + mode: '0644' diff --git a/template/0.3.0/ansible/cmn/setup_ufw.yml b/template/0.3.0/ansible/cmn/setup_ufw.yml new file mode 100644 index 0000000..27a92fd --- /dev/null +++ b/template/0.3.0/ansible/cmn/setup_ufw.yml @@ -0,0 +1,73 @@ +--- +- name: Setup UFW + hosts: all + become: yes + tasks: + - name: Ensure UFW is installed + apt: + name: ufw + state: present + + - name: Enable UFW and set default policies + ufw: + state: enabled + direction: incoming + policy: deny + + - name: Allow SSH + ufw: + rule: allow + name: OpenSSH + + - name: Allow port 53 (DNS) + ufw: + rule: allow + port: 53 + + - name: Allow port 8899 TCP + ufw: + rule: allow + port: 8899 + proto: tcp + + - name: Allow port 8899 UDP + ufw: + rule: allow + port: 8899 + proto: udp + + - name: Allow ports 8000-8898 TCP + ufw: + rule: allow + port: '8000:8898' + proto: tcp + + - name: Allow ports 8000-8898 UDP + ufw: + rule: allow + port: '8000:8898' + proto: udp + + - name: Allow ports 8900-9999 TCP + ufw: + rule: allow + port: '8900:9999' + proto: tcp + + - name: Allow ports 8900-9999 UDP + ufw: + rule: allow + port: '8900:9999' + proto: udp + + - name: Allow port 10000 TCP + ufw: + rule: allow + port: 10000 + proto: tcp + + - name: Allow port 10000 UDP + ufw: + rule: allow + port: 10000 + proto: udp diff --git a/template/0.3.0/ansible/cmn/update_ubuntu.yml b/template/0.3.0/ansible/cmn/update_ubuntu.yml new file mode 100644 index 0000000..2fc2ccb --- /dev/null +++ b/template/0.3.0/ansible/cmn/update_ubuntu.yml @@ -0,0 +1,55 @@ +- name: Setup Firedancer + hosts: all + become: yes + tasks: + - name: Check Ubuntu version + debug: + msg: "Ubuntu version: {{ ansible_distribution_version }}" + + - name: Create ubuntu.sources file if Ubuntu version is 24.0 or later and less than 25.0 + copy: + content: | + Types: deb + URIs: http://nl.archive.ubuntu.com/ubuntu/ + Suites: noble noble-updates noble-backports + Components: main restricted universe multiverse + Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg + + Types: deb + URIs: http://security.ubuntu.com/ubuntu/ + Suites: noble-security + Components: main restricted universe multiverse + Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg + dest: /etc/apt/sources.list.d/ubuntu.sources + owner: root + group: root + mode: "0644" + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + + - name: Remove /etc/apt/sources.list + file: + path: /etc/apt/sources.list + state: absent + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + + - name: Add explanation to /etc/apt/sources.list + copy: + content: "# Ubuntu sources have moved to /etc/apt/sources.list.d/ubuntu.sources\n" + dest: /etc/apt/sources.list + owner: root + group: root + mode: "0644" + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + - name: Update package lists + apt: + update_cache: yes + + - name: Upgrade packages + apt: + upgrade: dist diff --git a/template/0.3.0/ansible/testnet-validator/change_identity_and_restart.yml b/template/0.3.0/ansible/testnet-validator/change_identity_and_restart.yml new file mode 100644 index 0000000..acd5ef4 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/change_identity_and_restart.yml @@ -0,0 +1,27 @@ +--- +- name: Change Identity and Restart firedancer.service + hosts: all + become: true + become_user: solv + + tasks: + - name: Stop firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: stopped + become: true + become_user: root + register: stop_service_result + + - name: Change Symbolic Link to Authoried Identity + shell: ln -sf /home/solv/testnet-validator-keypair.json /home/solv/identity.json + become: true + become_user: solv + + - name: Start firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: started + become: true + become_user: root + register: start_service_result diff --git a/template/0.3.0/ansible/testnet-validator/copy_keys.yml b/template/0.3.0/ansible/testnet-validator/copy_keys.yml new file mode 100644 index 0000000..2a88666 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/copy_keys.yml @@ -0,0 +1,37 @@ +--- +- name: Copy validator and vote key pairs + hosts: all + gather_facts: no + vars_files: + - ~/.slv/config.validator.testnet.yml + + tasks: + - name: Select node config by identity_account + set_fact: + validators: >- + {{ + validators + | selectattr('identity_account', 'equalto', hostvars[inventory_hostname].identity_account) + | list + | first + }} + + - name: Copy validator key file + become: yes + become_user: root + copy: + src: "~/.slv/keys/{{ validators.identity_account }}.json" + dest: "/home/solv/testnet-validator-keypair.json" + owner: solv + group: solv + mode: "0600" + + - name: Copy vote key file + become: yes + become_user: root + copy: + src: "~/.slv/keys/{{ validators.vote_account }}.json" + dest: "/home/solv/testnet-vote-account-keypair.json" + owner: solv + group: solv + mode: "0600" diff --git a/template/0.3.0/ansible/testnet-validator/init.yml b/template/0.3.0/ansible/testnet-validator/init.yml new file mode 100644 index 0000000..711b20b --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/init.yml @@ -0,0 +1,14 @@ +--- +- import_playbook: copy_keys.yml +- import_playbook: ../cmn/setup_ufw.yml +- import_playbook: ../cmn/install_package.yml +- import_playbook: ../cmn/install_rust.yml +- import_playbook: ../cmn/install_agave.yml +- import_playbook: ../cmn/mount_disks.yml +- import_playbook: ../cmn/optimize_system.yml +- import_playbook: ../cmn/setup_norestart.yml +- import_playbook: ../cmn/setup-logrotate.yml +- import_playbook: ../cmn/setup-unstaked-identity.yml +- import_playbook: setup_firedancer.yml +- import_playbook: setup_snapshot_finder.yml +- import_playbook: start_firedancer.yml diff --git a/template/0.3.0/ansible/testnet-validator/restart-firedancer.yml b/template/0.3.0/ansible/testnet-validator/restart-firedancer.yml new file mode 100644 index 0000000..ceccd0b --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/restart-firedancer.yml @@ -0,0 +1,3 @@ +--- +- import_playbook: stop-firedancer.yml +- import_playbook: start-firedancer.yml diff --git a/template/0.3.0/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml b/template/0.3.0/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml new file mode 100644 index 0000000..15f01a0 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml @@ -0,0 +1,62 @@ +--- +- name: Execute tasks as solv user + hosts: all + become: true + become_user: solv + vars_files: + - vars/config.yml + tasks: + - name: Stop firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: stopped + become: true + become_user: root + register: stop_service_result + + - name: Display stop service result + ansible.builtin.debug: + msg: '{{ inventory_hostname }}: Stopped firedancer.service.' + + - name: Remove all files in /mnt/ledger/ using rm -rf + ansible.builtin.shell: rm -rf /mnt/ledger/* + become: true + become_user: root + + - name: Remove All Snapshot Archives + ansible.builtin.shell: rm -rf /mnt/snapshot/* + become: true + become_user: root + + - name: Find snapshot configuration for this host by identity_account + set_fact: + snapshot_config: '{{ item }}' + with_items: '{{ validators }}' + when: item.identity_account == identity_account + + - name: Debug selected snapshot configuration + debug: + var: snapshot_config + + - name: Run Snapshot Finder + shell: | + source ./venv/bin/activate && python3 snapshot-finder.py --snapshot_path /mnt/snapshot --version {{ snapshot_config.solana_version }} -r http://api.testnet.solana.com --min_download_speed 45 + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + async: 3600 + poll: 10 + + - name: Start firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: started + become: true + become_user: root + register: start_service_result + + - name: Display start service result + ansible.builtin.debug: + msg: '{{ inventory_hostname }}: Started firedancer.service.' diff --git a/template/0.3.0/ansible/testnet-validator/set_identity_key.yml b/template/0.3.0/ansible/testnet-validator/set_identity_key.yml new file mode 100644 index 0000000..1212320 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/set_identity_key.yml @@ -0,0 +1,10 @@ +--- +- name: Set Authoried Key to Identity + hosts: all + become: true + become_user: solv + tasks: + - name: Change Symbolic Link to Authoried Identity + shell: ln -sf /home/solv/testnet-validator-keypair.json /home/solv/identity.json + become: true + become_user: solv diff --git a/template/0.3.0/ansible/testnet-validator/set_unstaked_key.yml b/template/0.3.0/ansible/testnet-validator/set_unstaked_key.yml new file mode 100644 index 0000000..d8b2acd --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/set_unstaked_key.yml @@ -0,0 +1,10 @@ +--- +- name: Set Unstaked Key to Identity + hosts: all + become: true + become_user: solv + tasks: + - name: Change Symbolic Link to Unstaked Identity + shell: ln -sf /home/solv/unstaked-identity.json /home/solv/identity.json + become: true + become_user: solv diff --git a/template/0.3.0/ansible/testnet-validator/setup_firedancer.yml b/template/0.3.0/ansible/testnet-validator/setup_firedancer.yml new file mode 100644 index 0000000..3c379f7 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/setup_firedancer.yml @@ -0,0 +1,117 @@ +--- +- name: Setup Firedancer + hosts: all + become: true + become_user: solv + tasks: + - name: Clone Firedancer repository + git: + repo: https://github.com/firedancer-io/firedancer.git + dest: /home/solv/firedancer + update: yes + force: yes + version: v0.302.20104 + + - name: Initialize git submodules + command: git submodule update --init --recursive + args: + chdir: /home/solv/firedancer + + - name: Install rustup + shell: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + args: + chdir: /home/solv + become: yes + become_user: solv + + - name: Ensure rustup is in PATH + shell: | + echo 'source $HOME/.cargo/env' >> ~/.bashrc + . $HOME/.cargo/env + export PATH="$HOME/.cargo/bin:$PATH" + args: + executable: /bin/bash + become: yes + become_user: solv + + - name: Install Rust 1.81.0 + shell: | + export PATH="$HOME/.cargo/bin:$PATH" + rustup install 1.81.0 + args: + chdir: /home/solv/firedancer + become: yes + become_user: solv + + - name: Run deps.sh script with yes + shell: | + yes y | ./deps.sh + args: + chdir: /home/solv/firedancer + + - name: Build Firedancer with make + shell: | + export PATH="$HOME/.cargo/bin:$PATH" + . $HOME/.cargo/env + make -j fdctl solana + args: + chdir: /home/solv/firedancer + executable: /bin/bash + async: 3600 + poll: 30 + + - name: Create symbolic link for fdctl + shell: sudo ln -s /home/solv/firedancer/build/native/gcc/bin/fdctl /usr/local/bin/fdctl + args: + creates: /usr/local/bin/fdctl + + - name: Create config.toml + template: + src: ~/.slv/testnet-validator/firedancer-config.toml.j2 + dest: /home/solv/firedancer/firedancer-config.toml + owner: solv + group: solv + mode: '0644' + + - name: Generate start-firedancer.sh from template + template: + src: ~/.slv/testnet-validator/start-firedancer.sh.j2 + dest: /home/solv/start-firedancer.sh + mode: '0755' + owner: solv + group: solv + + - name: Create Firedancer systemd service file with sudo tee + shell: | + echo "[Unit] + Description=Firedancer Solana + After=network.target + StartLimitIntervalSec=0 + + [Service] + Type=simple + Restart=always + RestartSec=1 + User=solv + LimitNOFILE=1000000 + LogRateLimitIntervalSec=0 + ExecStart=/home/solv/start-firedancer.sh + + [Install] + WantedBy=multi-user.target" | sudo tee /etc/systemd/system/firedancer.service > /dev/null + become: yes + become_user: solv + + - name: Reload systemd daemon + command: systemctl daemon-reload + become: yes + become_user: root + + - name: Enable Firedancer + shell: sudo systemctl enable firedancer + become: yes + become_user: root + + - name: Start Firedancer + shell: . ~/.profile && sudo systemctl start firedancer + register: command_output diff --git a/template/0.3.0/ansible/testnet-validator/setup_snapshot_finder.yml b/template/0.3.0/ansible/testnet-validator/setup_snapshot_finder.yml new file mode 100644 index 0000000..6989901 --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/setup_snapshot_finder.yml @@ -0,0 +1,80 @@ +--- +- name: Setup Solana Snapshot Finder + hosts: all + become: yes + vars_files: + - ~/.slv/config.validator.testnet.yml + tasks: + # 1. Ensure pip3 and venv are installed + - name: Install pip3 and venv if not present + apt: + name: + - python3-pip + - python3-venv + state: present + become: yes + + # 2. Check if Solana Snapshot Finder directory exists + - name: Check if Solana Snapshot Finder directory exists + stat: + path: /home/solv/solana-snapshot-finder + register: snapshot_finder_dir + + # 3. Gitリポジトリのクローン + - name: Clone Solana Snapshot Finder repository + git: + repo: https://github.com/c29r3/solana-snapshot-finder.git + dest: /home/solv/solana-snapshot-finder + update: yes + when: not snapshot_finder_dir.stat.exists + + # 4. Ensure correct ownership of solana-snapshot-finder directory + - name: Ensure correct ownership of solana-snapshot-finder directory + file: + path: /home/solv/solana-snapshot-finder + state: directory + owner: solv + group: solv + recurse: yes + + # 5. Create virtual environment + - name: Create virtual environment + shell: | + python3 -m venv venv + args: + chdir: /home/solv/solana-snapshot-finder + become: false + become_user: solv + + # 6. Activate virtual environment and install requirements + - name: Install Python dependencies in virtual environment + shell: | + source ./venv/bin/activate && pip3 install -r requirements.txt + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + + # 7. Find snapshot configuration for this host by identity_account + - name: Find snapshot configuration for this host by identity_account + set_fact: + snapshot_config: '{{ item }}' + with_items: '{{ validators }}' + when: item.identity_account == identity_account + + - name: Debug selected snapshot configuration + debug: + var: snapshot_config + + # 8. Run Snapshot Finder + - name: Run Snapshot Finder + shell: | + source ./venv/bin/activate && python3 snapshot-finder.py --snapshot_path /mnt/snapshot --version {{ snapshot_config.solana_version }} -r http://api.testnet.solana.com --min_download_speed 45 + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + async: 3600 + poll: 10 diff --git a/template/0.3.0/ansible/testnet-validator/start_firedancer.yml b/template/0.3.0/ansible/testnet-validator/start_firedancer.yml new file mode 100644 index 0000000..fc5964d --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/start_firedancer.yml @@ -0,0 +1,13 @@ +--- +- name: Run Command + hosts: all + become: true + become_user: solv + tasks: + - name: Start Firedancer + shell: . ~/.profile && sudo systemctl start firedancer + register: command_output + + - name: Display Command output + debug: + msg: "{{ inventory_hostname }} ({{ hostvars[inventory_hostname]['name'] }}): Start Firedancer: {{ command_output.stdout }}" diff --git a/template/0.3.0/ansible/testnet-validator/stop_firedancer.yml b/template/0.3.0/ansible/testnet-validator/stop_firedancer.yml new file mode 100644 index 0000000..8ac43de --- /dev/null +++ b/template/0.3.0/ansible/testnet-validator/stop_firedancer.yml @@ -0,0 +1,13 @@ +--- +- name: Run Command + hosts: all + become: true + become_user: solv + tasks: + - name: Stop Firedancer + shell: . ~/.profile && sudo systemctl stop firedancer + register: command_output + + - name: Display Command output + debug: + msg: "{{ inventory_hostname }} ({{ hostvars[inventory_hostname]['name'] }}): Stop Firedancer: {{ command_output.stdout }}" diff --git a/template/0.3.0/jinja/testnet-validator/firedancer-config.toml.j2 b/template/0.3.0/jinja/testnet-validator/firedancer-config.toml.j2 new file mode 100644 index 0000000..6f29594 --- /dev/null +++ b/template/0.3.0/jinja/testnet-validator/firedancer-config.toml.j2 @@ -0,0 +1,68 @@ +name = "solv" +user = "solv" +scratch_directory = "/home/{user}" +dynamic_port_range = "8900-9000" + +[log] + path = "/home/solv/solana-validator.log" + colorize = "auto" + level_logfile = "INFO" + level_stderr = "NOTICE" + level_flush = "WARNING" + +[rpc] + port = 8899 + full_api = true + private = true + +[reporting] + solana_metrics_config = "host=https://metrics.solana.com:8086,db=tds,u=testnet_write,p=c4fa841aa918bf8274e3e2a44d77568d9861b3ea" + +[ledger] + path = "/mnt/ledger" + accounts_path = "/mnt/accounts" + limit_size = 200_000_000 + account_indexes = [] + account_index_exclude_keys = [] + snapshot_archive_format = "zstd" + require_tower = false + +[snapshots] + incremental_snapshots = true + full_snapshot_interval_slots = 25000 + incremental_snapshot_interval_slots = 100 + path = "/mnt/snapshot" + +[gossip] + entrypoints = [ + "entrypoint.testnet.solana.com:8001", + "entrypoint2.testnet.solana.com:8001", + "entrypoint3.testnet.solana.com:8001", + ] + +[consensus] + identity_path = "/home/solv/identity.json" + vote_account_path = "/home/solv/testnet-vote-account-keypair.json" + authorized_voter_paths = [ + "/home/solv/testnet-validator-keypair.json" + ] + snapshot_fetch = true + genesis_fetch = true + expected_genesis_hash = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY" + expected_bank_hash = "BiGFLfFewfTB2asBRLjwRL6z7VNfuvYraS3H7RfQNCrf" + expected_shred_version = 64506 + known_validators = [ + "5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on", + ] + +[layout] + affinity = "auto" + agave_affinity = "auto" + net_tile_count = 1 + quic_tile_count = 1 + verify_tile_count = 4 + bank_tile_count = 2 + shred_tile_count = 1 + +[hugetlbfs] + mount_path = "/mnt" \ No newline at end of file diff --git a/template/0.3.0/jinja/testnet-validator/start-firedancer.sh.j2 b/template/0.3.0/jinja/testnet-validator/start-firedancer.sh.j2 new file mode 100644 index 0000000..bcdbfe9 --- /dev/null +++ b/template/0.3.0/jinja/testnet-validator/start-firedancer.sh.j2 @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +sudo chmod -R 700 /mnt +sudo fdctl configure init all --config /home/solv/firedancer/firedancer-config.toml +sudo chown -R solv:solv /mnt +sudo fdctl run --config /home/solv/firedancer/firedancer-config.toml \ No newline at end of file diff --git a/template/0.3.1/ansible/cmn/create_user.yml b/template/0.3.1/ansible/cmn/create_user.yml new file mode 100644 index 0000000..397d403 --- /dev/null +++ b/template/0.3.1/ansible/cmn/create_user.yml @@ -0,0 +1,69 @@ +--- +- name: Create solv user with specific password and configure SSH access + hosts: all + become: yes + vars: + home_paths_authorized_keys: /home/solv/.ssh/authorized_keys + ansible_remote_tmp: /tmp/ansible_tmp + local_public_key_path: "{{ lookup('env', 'HOME') + '/.ssh/id_rsa.pub' }}" + vars_files: + - ~/.slv/config.pwd.yml + tasks: + - name: Ensure solv user exists + user: + name: solv + password: "{{ encrypted_password }}" + state: present + shell: /bin/bash + + - name: Ensure .ssh directory exists for solv user + file: + path: /home/solv/.ssh + state: directory + owner: solv + group: solv + mode: "0700" + + - name: Add local public key to authorized_keys + lineinfile: + path: "{{ home_paths_authorized_keys }}" + line: "{{ lookup('file', local_public_key_path) }}" + create: yes + owner: solv + group: solv + mode: "0600" + + - name: Generate SSH key for solv user if not exists + shell: su - solv -c "ssh-keygen -t rsa -b 4096 -N '' -f /home/solv/.ssh/id_rsa" + args: + creates: /home/solv/.ssh/id_rsa + + - name: Ensure correct permissions for .ssh directory + file: + path: /home/solv/.ssh + state: directory + owner: solv + group: solv + mode: "0700" + + - name: Ensure correct permissions for authorized_keys + file: + path: "{{ home_paths_authorized_keys }}" + state: file + owner: solv + group: solv + mode: "0600" + + - name: Add solv user to sudoers group + user: + name: solv + groups: sudo + append: yes + + - name: Configure sudoers file for solv user (no password required) + lineinfile: + path: /etc/sudoers + state: present + regexp: '^solv ALL=\(ALL\) NOPASSWD:ALL' + line: "solv ALL=(ALL) NOPASSWD:ALL" + validate: "visudo -cf %s" diff --git a/template/0.3.1/ansible/cmn/find_unmounted_disks.sh b/template/0.3.1/ansible/cmn/find_unmounted_disks.sh new file mode 100644 index 0000000..421e13b --- /dev/null +++ b/template/0.3.1/ansible/cmn/find_unmounted_disks.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +find_unmounted_nvme_disks() { + lsblk -nr -o NAME,TYPE,SIZE,MOUNTPOINT | awk ' + $2 == "disk" && + $1 ~ /^nvme/ && + (($3 ~ /G$/ && substr($3, 1, length($3)-1) + 0 >= 800) || + ($3 ~ /T$/ && substr($3, 1, length($3)-1) + 0 >= 0.8)) && + ($4 == "" || $4 ~ /^[[:space:]]*$/) && + system("lsblk -nr -o TYPE /dev/" $1 " | grep -q part") != 0 {print $1}' +} + +find_unmounted_nvme_disks diff --git a/template/0.3.1/ansible/cmn/install_agave.yml b/template/0.3.1/ansible/cmn/install_agave.yml new file mode 100644 index 0000000..2e40793 --- /dev/null +++ b/template/0.3.1/ansible/cmn/install_agave.yml @@ -0,0 +1,33 @@ +--- +- name: Install Agave + hosts: all + become: true + vars_files: + - ~/.slv/config.validator.testnet.yml + vars: + solana_bin_dir: '/home/solv/.local/share/solana/install/active_release/bin' + tasks: + - name: Select node config by identity_account + set_fact: + selected_validator: '{{ item }}' + loop: '{{ validators }}' + when: item.identity_account == hostvars[inventory_hostname].identity_account + + - name: Install Agave from the specified version + shell: | + tag=v{{ selected_validator.solana_version }} + curl -sSfL https://release.anza.xyz/${tag}/install | sh + args: + executable: /bin/bash + environment: + PATH: '{{ solana_bin_dir }}:{{ ansible_env.PATH }}' + HOME: '/home/solv' + + - name: Verify Solana installation + shell: '{{ solana_bin_dir }}/solana --version' + register: solana_version_check + failed_when: solana_version_check.rc != 0 + + - name: Debug Solana version check + debug: + var: solana_version_check.stdout diff --git a/template/0.3.1/ansible/cmn/install_package.yml b/template/0.3.1/ansible/cmn/install_package.yml new file mode 100644 index 0000000..d9c6348 --- /dev/null +++ b/template/0.3.1/ansible/cmn/install_package.yml @@ -0,0 +1,43 @@ +--- +- name: Install required packages + hosts: all + become: yes + tasks: + - name: Fix broken packages (optional) + become: yes + command: apt-get dist-upgrade -y + - name: Update apt cache + apt: + update_cache: yes + tags: setup_lib + + - name: Install fail2ban + apt: + name: fail2ban + state: present + tags: setup_lib + + - name: Ensure fail2ban is started and enabled + service: + name: fail2ban + state: started + enabled: yes + tags: setup_lib + + - name: Install development tools and libraries + apt: + name: + - libsasl2-dev + - build-essential + - libssl-dev + - libudev-dev + - pkg-config + - zlib1g-dev + - llvm + - clang + - cmake + - make + - libprotobuf-dev + - protobuf-compiler + state: present + tags: setup_lib diff --git a/template/0.3.1/ansible/cmn/install_rust.yml b/template/0.3.1/ansible/cmn/install_rust.yml new file mode 100644 index 0000000..383cd0b --- /dev/null +++ b/template/0.3.1/ansible/cmn/install_rust.yml @@ -0,0 +1,57 @@ +--- +- name: Install Rust and Cargo + hosts: all + become: true + vars: + rust_url: 'https://sh.rustup.rs' + tasks: + - name: Ensure Rust installer is downloaded + get_url: + url: '{{ rust_url }}' + dest: /tmp/rustup-install.sh + mode: 0755 + + - name: Install Rust + shell: | + sudo -u solv /tmp/rustup-install.sh -y + args: + chdir: /home/solv/ + register: install_output + + - name: Debug Rust installation output + debug: + var: install_output + + - name: Add Cargo bin directory to PATH manually + shell: | + echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> /home/solv/.bashrc + args: + executable: /bin/bash + + - name: Source .bashrc to ensure PATH is updated + shell: | + sudo -i -u solv bash -c "source ~/.bashrc" + args: + executable: /bin/bash + + - name: Install rustfmt component + shell: | + sudo -i -u solv bash -c "rustup component add rustfmt" + environment: + PATH: /bin:/usr/bin:/usr/local/bin:/home/solv/.cargo/bin + register: rustup_output + + - name: Debug rustfmt installation output + debug: + var: rustup_output + + - name: Verify Rust installation + shell: | + sudo -i -u solv bash -c "cargo --version" + environment: + PATH: /bin:/usr/bin:/usr/local/bin:/home/solv/.cargo/bin + register: cargo_version + + - name: Debug Rust version + debug: + var: cargo_version diff --git a/template/0.3.1/ansible/cmn/mount_disks.yml b/template/0.3.1/ansible/cmn/mount_disks.yml new file mode 100644 index 0000000..7f1175e --- /dev/null +++ b/template/0.3.1/ansible/cmn/mount_disks.yml @@ -0,0 +1,107 @@ +--- +- name: Mount and configure disks with NVMe prioritization + hosts: all + become: true + vars: + mount_dirs: + - /mnt + - /mnt/ledger + - /mnt/accounts + - /mnt/snapshot + tasks: + - name: Ensure swap is disabled + block: + - name: Check for active swap + shell: swapon --show --noheadings --output NAME | awk '{print $1}' + register: active_swap + changed_when: false + + - name: Debug active swap devices + debug: + var: active_swap.stdout_lines + + - name: Disable active swap devices + command: swapoff {{ item }} + loop: "{{ active_swap.stdout_lines }}" + when: active_swap.stdout_lines | length > 0 + + - name: Remove swap entries from /etc/fstab + replace: + path: /etc/fstab + regexp: ".*swap.*" + replace: "" + when: active_swap.stdout_lines | length > 0 + + - name: Ensure /mnt and subdirectories exist with correct ownership + file: + path: "{{ item }}" + state: directory + owner: solv + group: solv + mode: "0755" + loop: "{{ mount_dirs }}" + + - name: Execute shell script to find unmounted disks + shell: | + lsblk -nr -o NAME,TYPE,SIZE,MOUNTPOINT | awk ' + $2 == "disk" && + $1 ~ /^nvme/ && + (($3 ~ /G$/ && substr($3, 1, length($3)-1) + 0 >= 800) || + ($3 ~ /T$/ && substr($3, 1, length($3)-1) + 0 >= 0.8)) && + ($4 == "" || $4 ~ /^[[:space:]]*$/) && + system("lsblk -nr -o TYPE /dev/" $1 " | grep -q part") != 0 {print $1}' + register: unmounted_disks_output + args: + chdir: /home/solv + + - name: Debug unmounted disks + debug: + var: unmounted_disks_output.stdout_lines + + - name: Remove the find_unmounted_disks.sh script + file: + path: /home/solv/find_unmounted_disks.sh + state: absent + + - name: Skip if no unmounted disks found + debug: + msg: "No unmounted disks found, skipping disk formatting and mounting tasks." + when: unmounted_disks_output.stdout_lines | length == 0 + + - name: Mount and format disks dynamically + block: + - name: Format disk to ext4 if not already formatted + filesystem: + fstype: ext4 + dev: "/dev/{{ item }}" + loop: "{{ unmounted_disks_output.stdout_lines }}" + loop_control: + label: "{{ item }}" + + - name: Mount /mnt/ledger + mount: + path: /mnt/ledger + src: "/dev/{{ unmounted_disks_output.stdout_lines[0] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 1 + + - name: Mount /mnt/accounts + mount: + path: /mnt/accounts + src: "/dev/{{ unmounted_disks_output.stdout_lines[1] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 2 + + - name: Mount /mnt/snapshot + mount: + path: /mnt/snapshot + src: "/dev/{{ unmounted_disks_output.stdout_lines[2] }}" + fstype: ext4 + state: mounted + opts: defaults,noatime + when: unmounted_disks_output.stdout_lines | length >= 3 + when: unmounted_disks_output.stdout_lines | length > 0 diff --git a/template/0.3.1/ansible/cmn/optimize_system.yml b/template/0.3.1/ansible/cmn/optimize_system.yml new file mode 100644 index 0000000..0e3ee21 --- /dev/null +++ b/template/0.3.1/ansible/cmn/optimize_system.yml @@ -0,0 +1,64 @@ +--- +- name: Optimize system performance for Solana + hosts: all + become: true + tasks: + - name: Apply sysctl performance settings + blockinfile: + path: /etc/sysctl.conf + block: | + # TCP Buffer Sizes (10k min, 87.38k default, 12M max) + net.ipv4.tcp_rmem=10240 87380 12582912 + net.ipv4.tcp_wmem=10240 87380 12582912 + + # TCP Optimization + net.ipv4.tcp_congestion_control=westwood + net.ipv4.tcp_fastopen=3 + net.ipv4.tcp_timestamps=0 + net.ipv4.tcp_sack=1 + net.ipv4.tcp_low_latency=1 + net.ipv4.tcp_tw_reuse=1 + net.ipv4.tcp_no_metrics_save=1 + net.ipv4.tcp_moderate_rcvbuf=1 + + # Kernel Optimization + kernel.timer_migration=0 + kernel.hung_task_timeout_secs=30 + kernel.pid_max=49152 + + # Virtual Memory Tuning + vm.swappiness=30 + vm.max_map_count=2000000 + vm.stat_interval=10 + vm.dirty_ratio=40 + vm.dirty_background_ratio=10 + vm.min_free_kbytes=3000000 + vm.dirty_expire_centisecs=36000 + vm.dirty_writeback_centisecs=3000 + vm.dirtytime_expire_seconds=43200 + + # Solana Specific Tuning + net.core.rmem_max=134217728 + net.core.rmem_default=134217728 + net.core.wmem_max=134217728 + net.core.wmem_default=134217728 + marker: '# {mark} SOLANA SETTINGS' + notify: Reload sysctl + + - name: Install cpufrequtils for CPU governor management + apt: + name: cpufrequtils + state: present + + - name: Set CPU governor to performance in config file + lineinfile: + path: /etc/default/cpufrequtils + line: 'GOVERNOR="performance"' + create: yes + + - name: Set CPU governor to performance for all CPUs + shell: echo "performance" > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor || true + + handlers: + - name: Reload sysctl + command: sysctl -p diff --git a/template/0.3.1/ansible/cmn/setup-logrotate.yml b/template/0.3.1/ansible/cmn/setup-logrotate.yml new file mode 100644 index 0000000..050f7c9 --- /dev/null +++ b/template/0.3.1/ansible/cmn/setup-logrotate.yml @@ -0,0 +1,43 @@ +--- +- name: Configure log rotation for Solana validator + hosts: all + become: true + tasks: + - name: Ensure logrotate configuration directory exists + file: + path: /etc/logrotate.d + state: directory + mode: '0755' + owner: root + group: root + + - name: Create logrotate configuration for Solana validator + copy: + dest: /etc/logrotate.d/solana + content: | + /home/solv/solana-validator.log { + su solv solv + daily + rotate 1 + size 4G + missingok + compress + postrotate + systemctl kill -s USR1 solv.service + endscript + } + owner: root + group: root + mode: '0644' + + - name: Test logrotate configuration + command: logrotate --debug /etc/logrotate.conf + register: logrotate_debug + failed_when: "'error' in logrotate_debug.stderr" + + - name: Debug logrotate test output + debug: + var: logrotate_debug.stdout + + - name: Force logrotate to apply the new configuration + command: logrotate -f /etc/logrotate.conf diff --git a/template/0.3.1/ansible/cmn/setup-unstaked-identity.yml b/template/0.3.1/ansible/cmn/setup-unstaked-identity.yml new file mode 100644 index 0000000..574315b --- /dev/null +++ b/template/0.3.1/ansible/cmn/setup-unstaked-identity.yml @@ -0,0 +1,19 @@ +--- +- name: Create and link Solana identity files + hosts: all + become: true + tasks: + - name: Create unstaked-identity.json using solana-keygen + shell: | + sudo -u solv bash -c 'source ~/.profile && solana-keygen new --outfile /home/solv/unstaked-identity.json --no-passphrase' + args: + creates: /home/solv/unstaked-identity.json # Skip if file already exists + + - name: Ensure /home/solv/identity.json is a symlink to unstaked-identity.json + file: + src: /home/solv/unstaked-identity.json + dest: /home/solv/identity.json + state: link + + - name: Update ownership of identity.json symlink + command: chown --no-dereference solv:solv /home/solv/identity.json diff --git a/template/0.3.1/ansible/cmn/setup_norestart.yml b/template/0.3.1/ansible/cmn/setup_norestart.yml new file mode 100644 index 0000000..53ae99b --- /dev/null +++ b/template/0.3.1/ansible/cmn/setup_norestart.yml @@ -0,0 +1,26 @@ +--- +- name: Configure needrestart for solv.service + hosts: all + become: true + tasks: + - name: Ensure needrestart configuration directory exists + file: + path: /etc/needrestart/conf.d + state: directory + mode: '0755' + + - name: Configure needrestart to exclude solv.service + copy: + content: '$nrconf{override_rc}{qr(^solv\.service$)} = 0;' + dest: /etc/needrestart/conf.d/solv.conf + owner: root + group: root + mode: '0644' + + - name: Configure needrestart to exclude firedancer.service + copy: + content: '$nrconf{override_rc}{qr(^firedancer\.service$)} = 0;' + dest: /etc/needrestart/conf.d/firedancer.conf + owner: root + group: root + mode: '0644' diff --git a/template/0.3.1/ansible/cmn/setup_ufw.yml b/template/0.3.1/ansible/cmn/setup_ufw.yml new file mode 100644 index 0000000..27a92fd --- /dev/null +++ b/template/0.3.1/ansible/cmn/setup_ufw.yml @@ -0,0 +1,73 @@ +--- +- name: Setup UFW + hosts: all + become: yes + tasks: + - name: Ensure UFW is installed + apt: + name: ufw + state: present + + - name: Enable UFW and set default policies + ufw: + state: enabled + direction: incoming + policy: deny + + - name: Allow SSH + ufw: + rule: allow + name: OpenSSH + + - name: Allow port 53 (DNS) + ufw: + rule: allow + port: 53 + + - name: Allow port 8899 TCP + ufw: + rule: allow + port: 8899 + proto: tcp + + - name: Allow port 8899 UDP + ufw: + rule: allow + port: 8899 + proto: udp + + - name: Allow ports 8000-8898 TCP + ufw: + rule: allow + port: '8000:8898' + proto: tcp + + - name: Allow ports 8000-8898 UDP + ufw: + rule: allow + port: '8000:8898' + proto: udp + + - name: Allow ports 8900-9999 TCP + ufw: + rule: allow + port: '8900:9999' + proto: tcp + + - name: Allow ports 8900-9999 UDP + ufw: + rule: allow + port: '8900:9999' + proto: udp + + - name: Allow port 10000 TCP + ufw: + rule: allow + port: 10000 + proto: tcp + + - name: Allow port 10000 UDP + ufw: + rule: allow + port: 10000 + proto: udp diff --git a/template/0.3.1/ansible/cmn/update_ubuntu.yml b/template/0.3.1/ansible/cmn/update_ubuntu.yml new file mode 100644 index 0000000..2fc2ccb --- /dev/null +++ b/template/0.3.1/ansible/cmn/update_ubuntu.yml @@ -0,0 +1,55 @@ +- name: Setup Firedancer + hosts: all + become: yes + tasks: + - name: Check Ubuntu version + debug: + msg: "Ubuntu version: {{ ansible_distribution_version }}" + + - name: Create ubuntu.sources file if Ubuntu version is 24.0 or later and less than 25.0 + copy: + content: | + Types: deb + URIs: http://nl.archive.ubuntu.com/ubuntu/ + Suites: noble noble-updates noble-backports + Components: main restricted universe multiverse + Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg + + Types: deb + URIs: http://security.ubuntu.com/ubuntu/ + Suites: noble-security + Components: main restricted universe multiverse + Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg + dest: /etc/apt/sources.list.d/ubuntu.sources + owner: root + group: root + mode: "0644" + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + + - name: Remove /etc/apt/sources.list + file: + path: /etc/apt/sources.list + state: absent + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + + - name: Add explanation to /etc/apt/sources.list + copy: + content: "# Ubuntu sources have moved to /etc/apt/sources.list.d/ubuntu.sources\n" + dest: /etc/apt/sources.list + owner: root + group: root + mode: "0644" + when: > + (ansible_distribution_version is version('24.0', '>=')) and + (ansible_distribution_version is version('25.0', '<')) + - name: Update package lists + apt: + update_cache: yes + + - name: Upgrade packages + apt: + upgrade: dist diff --git a/template/0.3.1/ansible/testnet-validator/change_identity_and_restart.yml b/template/0.3.1/ansible/testnet-validator/change_identity_and_restart.yml new file mode 100644 index 0000000..acd5ef4 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/change_identity_and_restart.yml @@ -0,0 +1,27 @@ +--- +- name: Change Identity and Restart firedancer.service + hosts: all + become: true + become_user: solv + + tasks: + - name: Stop firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: stopped + become: true + become_user: root + register: stop_service_result + + - name: Change Symbolic Link to Authoried Identity + shell: ln -sf /home/solv/testnet-validator-keypair.json /home/solv/identity.json + become: true + become_user: solv + + - name: Start firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: started + become: true + become_user: root + register: start_service_result diff --git a/template/0.3.1/ansible/testnet-validator/copy_keys.yml b/template/0.3.1/ansible/testnet-validator/copy_keys.yml new file mode 100644 index 0000000..2a88666 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/copy_keys.yml @@ -0,0 +1,37 @@ +--- +- name: Copy validator and vote key pairs + hosts: all + gather_facts: no + vars_files: + - ~/.slv/config.validator.testnet.yml + + tasks: + - name: Select node config by identity_account + set_fact: + validators: >- + {{ + validators + | selectattr('identity_account', 'equalto', hostvars[inventory_hostname].identity_account) + | list + | first + }} + + - name: Copy validator key file + become: yes + become_user: root + copy: + src: "~/.slv/keys/{{ validators.identity_account }}.json" + dest: "/home/solv/testnet-validator-keypair.json" + owner: solv + group: solv + mode: "0600" + + - name: Copy vote key file + become: yes + become_user: root + copy: + src: "~/.slv/keys/{{ validators.vote_account }}.json" + dest: "/home/solv/testnet-vote-account-keypair.json" + owner: solv + group: solv + mode: "0600" diff --git a/template/0.3.1/ansible/testnet-validator/init.yml b/template/0.3.1/ansible/testnet-validator/init.yml new file mode 100644 index 0000000..711b20b --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/init.yml @@ -0,0 +1,14 @@ +--- +- import_playbook: copy_keys.yml +- import_playbook: ../cmn/setup_ufw.yml +- import_playbook: ../cmn/install_package.yml +- import_playbook: ../cmn/install_rust.yml +- import_playbook: ../cmn/install_agave.yml +- import_playbook: ../cmn/mount_disks.yml +- import_playbook: ../cmn/optimize_system.yml +- import_playbook: ../cmn/setup_norestart.yml +- import_playbook: ../cmn/setup-logrotate.yml +- import_playbook: ../cmn/setup-unstaked-identity.yml +- import_playbook: setup_firedancer.yml +- import_playbook: setup_snapshot_finder.yml +- import_playbook: start_firedancer.yml diff --git a/template/0.3.1/ansible/testnet-validator/restart-firedancer.yml b/template/0.3.1/ansible/testnet-validator/restart-firedancer.yml new file mode 100644 index 0000000..ceccd0b --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/restart-firedancer.yml @@ -0,0 +1,3 @@ +--- +- import_playbook: stop-firedancer.yml +- import_playbook: start-firedancer.yml diff --git a/template/0.3.1/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml b/template/0.3.1/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml new file mode 100644 index 0000000..15f01a0 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/restart_firedancer_with_rm_ledger.yml @@ -0,0 +1,62 @@ +--- +- name: Execute tasks as solv user + hosts: all + become: true + become_user: solv + vars_files: + - vars/config.yml + tasks: + - name: Stop firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: stopped + become: true + become_user: root + register: stop_service_result + + - name: Display stop service result + ansible.builtin.debug: + msg: '{{ inventory_hostname }}: Stopped firedancer.service.' + + - name: Remove all files in /mnt/ledger/ using rm -rf + ansible.builtin.shell: rm -rf /mnt/ledger/* + become: true + become_user: root + + - name: Remove All Snapshot Archives + ansible.builtin.shell: rm -rf /mnt/snapshot/* + become: true + become_user: root + + - name: Find snapshot configuration for this host by identity_account + set_fact: + snapshot_config: '{{ item }}' + with_items: '{{ validators }}' + when: item.identity_account == identity_account + + - name: Debug selected snapshot configuration + debug: + var: snapshot_config + + - name: Run Snapshot Finder + shell: | + source ./venv/bin/activate && python3 snapshot-finder.py --snapshot_path /mnt/snapshot --version {{ snapshot_config.solana_version }} -r http://api.testnet.solana.com --min_download_speed 45 + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + async: 3600 + poll: 10 + + - name: Start firedancer.service + ansible.builtin.systemd: + name: firedancer.service + state: started + become: true + become_user: root + register: start_service_result + + - name: Display start service result + ansible.builtin.debug: + msg: '{{ inventory_hostname }}: Started firedancer.service.' diff --git a/template/0.3.1/ansible/testnet-validator/set_identity_key.yml b/template/0.3.1/ansible/testnet-validator/set_identity_key.yml new file mode 100644 index 0000000..1212320 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/set_identity_key.yml @@ -0,0 +1,10 @@ +--- +- name: Set Authoried Key to Identity + hosts: all + become: true + become_user: solv + tasks: + - name: Change Symbolic Link to Authoried Identity + shell: ln -sf /home/solv/testnet-validator-keypair.json /home/solv/identity.json + become: true + become_user: solv diff --git a/template/0.3.1/ansible/testnet-validator/set_unstaked_key.yml b/template/0.3.1/ansible/testnet-validator/set_unstaked_key.yml new file mode 100644 index 0000000..d8b2acd --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/set_unstaked_key.yml @@ -0,0 +1,10 @@ +--- +- name: Set Unstaked Key to Identity + hosts: all + become: true + become_user: solv + tasks: + - name: Change Symbolic Link to Unstaked Identity + shell: ln -sf /home/solv/unstaked-identity.json /home/solv/identity.json + become: true + become_user: solv diff --git a/template/0.3.1/ansible/testnet-validator/setup_firedancer.yml b/template/0.3.1/ansible/testnet-validator/setup_firedancer.yml new file mode 100644 index 0000000..3c379f7 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/setup_firedancer.yml @@ -0,0 +1,117 @@ +--- +- name: Setup Firedancer + hosts: all + become: true + become_user: solv + tasks: + - name: Clone Firedancer repository + git: + repo: https://github.com/firedancer-io/firedancer.git + dest: /home/solv/firedancer + update: yes + force: yes + version: v0.302.20104 + + - name: Initialize git submodules + command: git submodule update --init --recursive + args: + chdir: /home/solv/firedancer + + - name: Install rustup + shell: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + args: + chdir: /home/solv + become: yes + become_user: solv + + - name: Ensure rustup is in PATH + shell: | + echo 'source $HOME/.cargo/env' >> ~/.bashrc + . $HOME/.cargo/env + export PATH="$HOME/.cargo/bin:$PATH" + args: + executable: /bin/bash + become: yes + become_user: solv + + - name: Install Rust 1.81.0 + shell: | + export PATH="$HOME/.cargo/bin:$PATH" + rustup install 1.81.0 + args: + chdir: /home/solv/firedancer + become: yes + become_user: solv + + - name: Run deps.sh script with yes + shell: | + yes y | ./deps.sh + args: + chdir: /home/solv/firedancer + + - name: Build Firedancer with make + shell: | + export PATH="$HOME/.cargo/bin:$PATH" + . $HOME/.cargo/env + make -j fdctl solana + args: + chdir: /home/solv/firedancer + executable: /bin/bash + async: 3600 + poll: 30 + + - name: Create symbolic link for fdctl + shell: sudo ln -s /home/solv/firedancer/build/native/gcc/bin/fdctl /usr/local/bin/fdctl + args: + creates: /usr/local/bin/fdctl + + - name: Create config.toml + template: + src: ~/.slv/testnet-validator/firedancer-config.toml.j2 + dest: /home/solv/firedancer/firedancer-config.toml + owner: solv + group: solv + mode: '0644' + + - name: Generate start-firedancer.sh from template + template: + src: ~/.slv/testnet-validator/start-firedancer.sh.j2 + dest: /home/solv/start-firedancer.sh + mode: '0755' + owner: solv + group: solv + + - name: Create Firedancer systemd service file with sudo tee + shell: | + echo "[Unit] + Description=Firedancer Solana + After=network.target + StartLimitIntervalSec=0 + + [Service] + Type=simple + Restart=always + RestartSec=1 + User=solv + LimitNOFILE=1000000 + LogRateLimitIntervalSec=0 + ExecStart=/home/solv/start-firedancer.sh + + [Install] + WantedBy=multi-user.target" | sudo tee /etc/systemd/system/firedancer.service > /dev/null + become: yes + become_user: solv + + - name: Reload systemd daemon + command: systemctl daemon-reload + become: yes + become_user: root + + - name: Enable Firedancer + shell: sudo systemctl enable firedancer + become: yes + become_user: root + + - name: Start Firedancer + shell: . ~/.profile && sudo systemctl start firedancer + register: command_output diff --git a/template/0.3.1/ansible/testnet-validator/setup_snapshot_finder.yml b/template/0.3.1/ansible/testnet-validator/setup_snapshot_finder.yml new file mode 100644 index 0000000..6989901 --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/setup_snapshot_finder.yml @@ -0,0 +1,80 @@ +--- +- name: Setup Solana Snapshot Finder + hosts: all + become: yes + vars_files: + - ~/.slv/config.validator.testnet.yml + tasks: + # 1. Ensure pip3 and venv are installed + - name: Install pip3 and venv if not present + apt: + name: + - python3-pip + - python3-venv + state: present + become: yes + + # 2. Check if Solana Snapshot Finder directory exists + - name: Check if Solana Snapshot Finder directory exists + stat: + path: /home/solv/solana-snapshot-finder + register: snapshot_finder_dir + + # 3. Gitリポジトリのクローン + - name: Clone Solana Snapshot Finder repository + git: + repo: https://github.com/c29r3/solana-snapshot-finder.git + dest: /home/solv/solana-snapshot-finder + update: yes + when: not snapshot_finder_dir.stat.exists + + # 4. Ensure correct ownership of solana-snapshot-finder directory + - name: Ensure correct ownership of solana-snapshot-finder directory + file: + path: /home/solv/solana-snapshot-finder + state: directory + owner: solv + group: solv + recurse: yes + + # 5. Create virtual environment + - name: Create virtual environment + shell: | + python3 -m venv venv + args: + chdir: /home/solv/solana-snapshot-finder + become: false + become_user: solv + + # 6. Activate virtual environment and install requirements + - name: Install Python dependencies in virtual environment + shell: | + source ./venv/bin/activate && pip3 install -r requirements.txt + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + + # 7. Find snapshot configuration for this host by identity_account + - name: Find snapshot configuration for this host by identity_account + set_fact: + snapshot_config: '{{ item }}' + with_items: '{{ validators }}' + when: item.identity_account == identity_account + + - name: Debug selected snapshot configuration + debug: + var: snapshot_config + + # 8. Run Snapshot Finder + - name: Run Snapshot Finder + shell: | + source ./venv/bin/activate && python3 snapshot-finder.py --snapshot_path /mnt/snapshot --version {{ snapshot_config.solana_version }} -r http://api.testnet.solana.com --min_download_speed 45 + args: + chdir: /home/solv/solana-snapshot-finder + executable: /bin/bash + become: false + become_user: solv + async: 3600 + poll: 10 diff --git a/template/0.3.1/ansible/testnet-validator/start_firedancer.yml b/template/0.3.1/ansible/testnet-validator/start_firedancer.yml new file mode 100644 index 0000000..fc5964d --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/start_firedancer.yml @@ -0,0 +1,13 @@ +--- +- name: Run Command + hosts: all + become: true + become_user: solv + tasks: + - name: Start Firedancer + shell: . ~/.profile && sudo systemctl start firedancer + register: command_output + + - name: Display Command output + debug: + msg: "{{ inventory_hostname }} ({{ hostvars[inventory_hostname]['name'] }}): Start Firedancer: {{ command_output.stdout }}" diff --git a/template/0.3.1/ansible/testnet-validator/stop_firedancer.yml b/template/0.3.1/ansible/testnet-validator/stop_firedancer.yml new file mode 100644 index 0000000..8ac43de --- /dev/null +++ b/template/0.3.1/ansible/testnet-validator/stop_firedancer.yml @@ -0,0 +1,13 @@ +--- +- name: Run Command + hosts: all + become: true + become_user: solv + tasks: + - name: Stop Firedancer + shell: . ~/.profile && sudo systemctl stop firedancer + register: command_output + + - name: Display Command output + debug: + msg: "{{ inventory_hostname }} ({{ hostvars[inventory_hostname]['name'] }}): Stop Firedancer: {{ command_output.stdout }}" diff --git a/template/0.3.1/jinja/testnet-validator/firedancer-config.toml.j2 b/template/0.3.1/jinja/testnet-validator/firedancer-config.toml.j2 new file mode 100644 index 0000000..6f29594 --- /dev/null +++ b/template/0.3.1/jinja/testnet-validator/firedancer-config.toml.j2 @@ -0,0 +1,68 @@ +name = "solv" +user = "solv" +scratch_directory = "/home/{user}" +dynamic_port_range = "8900-9000" + +[log] + path = "/home/solv/solana-validator.log" + colorize = "auto" + level_logfile = "INFO" + level_stderr = "NOTICE" + level_flush = "WARNING" + +[rpc] + port = 8899 + full_api = true + private = true + +[reporting] + solana_metrics_config = "host=https://metrics.solana.com:8086,db=tds,u=testnet_write,p=c4fa841aa918bf8274e3e2a44d77568d9861b3ea" + +[ledger] + path = "/mnt/ledger" + accounts_path = "/mnt/accounts" + limit_size = 200_000_000 + account_indexes = [] + account_index_exclude_keys = [] + snapshot_archive_format = "zstd" + require_tower = false + +[snapshots] + incremental_snapshots = true + full_snapshot_interval_slots = 25000 + incremental_snapshot_interval_slots = 100 + path = "/mnt/snapshot" + +[gossip] + entrypoints = [ + "entrypoint.testnet.solana.com:8001", + "entrypoint2.testnet.solana.com:8001", + "entrypoint3.testnet.solana.com:8001", + ] + +[consensus] + identity_path = "/home/solv/identity.json" + vote_account_path = "/home/solv/testnet-vote-account-keypair.json" + authorized_voter_paths = [ + "/home/solv/testnet-validator-keypair.json" + ] + snapshot_fetch = true + genesis_fetch = true + expected_genesis_hash = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY" + expected_bank_hash = "BiGFLfFewfTB2asBRLjwRL6z7VNfuvYraS3H7RfQNCrf" + expected_shred_version = 64506 + known_validators = [ + "5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on", + ] + +[layout] + affinity = "auto" + agave_affinity = "auto" + net_tile_count = 1 + quic_tile_count = 1 + verify_tile_count = 4 + bank_tile_count = 2 + shred_tile_count = 1 + +[hugetlbfs] + mount_path = "/mnt" \ No newline at end of file diff --git a/template/0.3.1/jinja/testnet-validator/start-firedancer.sh.j2 b/template/0.3.1/jinja/testnet-validator/start-firedancer.sh.j2 new file mode 100644 index 0000000..bcdbfe9 --- /dev/null +++ b/template/0.3.1/jinja/testnet-validator/start-firedancer.sh.j2 @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +sudo chmod -R 700 /mnt +sudo fdctl configure init all --config /home/solv/firedancer/firedancer-config.toml +sudo chown -R solv:solv /mnt +sudo fdctl run --config /home/solv/firedancer/firedancer-config.toml \ No newline at end of file