From ec4da7e8e520e4c6e8e62e0164823c860d67a4f8 Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Mon, 16 Mar 2026 22:32:11 +0000 Subject: [PATCH 1/4] ansible: add --prepare command to bootstrap remote servers Adds a new --prepare flag to spin-node.sh (ansible mode only) that verifies and installs the three prerequisites every remote host needs before a lean-quickstart deployment can run: - python3 (Ansible cannot self-bootstrap this) - Docker CE + Compose plugin (all clients run as containers) - yq (common role hard-fails without it) Changes: - parse-env.sh: add --prepare flag; bypass node-required guard - spin-node.sh: early-exit prepare path before genesis setup - run-ansible.sh: route prepare action to prepare.yml - ansible/playbooks/prepare.yml: new playbook targeting all:!localhost - README.md: document --prepare in Args, Scenarios, and Ansible sections --- README.md | 58 ++++++++++- ansible/playbooks/prepare.yml | 178 ++++++++++++++++++++++++++++++++++ parse-env.sh | 8 +- run-ansible.sh | 7 ++ spin-node.sh | 26 +++++ 5 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 ansible/playbooks/prepare.yml diff --git a/README.md b/README.md index 77970cf..acfc02a 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,41 @@ Every Ansible deployment automatically deploys an observability stack alongside - Example: Without flag, a random node will be selected automatically 13. `--checkpoint-sync-url` specifies the URL to fetch finalized checkpoint state from for checkpoint sync. Default: `https://leanpoint.leanroadmap.org/lean/v0/states/finalized`. Only used when `--restart-client` is specified. 14. `--restart-client` comma-separated list of client node names (e.g., `zeam_0,ream_0`). When specified, those clients are stopped, their data cleared, and restarted using checkpoint sync. Genesis is skipped. Use with `--checkpoint-sync-url` to override the default URL. +15. `--prepare` verify and install the software required to run lean nodes on every remote server. + - **Ansible mode only** — fails with an error if `deployment_mode` is not `ansible` + - Installs: `python3` (Ansible requirement), Docker CE + Compose plugin (all clients run as containers), `yq` (required by the `common` role at every deploy) + - Prints a per-tool, per-host status summary (`✅ ok` / `❌ missing`) + - `--node` is not required and is ignored; all other flags are also ignored except `--sshKey` and `--useRoot` + - Example: `NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare --sshKey ~/.ssh/id_ed25519 --useRoot` + +### Preparing remote servers + +Before deploying nodes to fresh remote servers for the first time, run `--prepare` to verify and install the three things every remote host needs: + +- **`python3`** — Ansible requires Python on managed nodes before any task can run; it cannot self-bootstrap this. If missing, `--prepare` fails immediately with a clear message. +- **Docker CE + Compose plugin** — every node client and the full observability stack runs as a Docker container. +- **`yq`** — the `common` role (which runs at every deploy) hard-fails if `yq` is not on the remote host. + +Prints a `✅` / `❌` status line per tool per host at the end. Fails if any required tool is still missing after the run. + +```sh +# Prepare all remote servers using the default SSH key +NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare + +# With a custom SSH key and root user +NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare --sshKey ~/.ssh/id_ed25519 --useRoot +``` + +**Constraints:** +- Only works in ansible mode (`deployment_mode: ansible` in your config, or `--deploymentMode ansible`) +- Any other flags (e.g., `--node`, `--generateGenesis`) are silently ignored — only `--sshKey` and `--useRoot` are used +- `--node` is not required; the playbook runs on all remote hosts in the inventory + +Once preparation succeeds, proceed with the normal deploy command: + +```sh +NETWORK_DIR=ansible-devnet ./spin-node.sh --node all --generateGenesis --sshKey ~/.ssh/id_ed25519 --useRoot +``` ### Checkpoint sync @@ -768,6 +803,7 @@ ansible/ │ └── all.yml # Global variables ├── playbooks/ │ ├── site.yml # Main playbook (clean + copy genesis + deploy) +│ ├── prepare.yml # Bootstrap: install Docker, build-essential, yq, etc. │ ├── clean-node-data.yml # Clean node data directories │ ├── generate-genesis.yml # Generate genesis files │ ├── copy-genesis.yml # Copy genesis files to remote hosts @@ -787,6 +823,26 @@ ansible/ └── ethlambda/ # EthLambda node role ``` +### Bootstrapping remote servers + +Fresh servers need Docker, build tools, and utilities installed before any lean node can be deployed. Run `--prepare` once per set of servers: + +```sh +NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare --sshKey ~/.ssh/id_ed25519 --useRoot +``` + +The command runs `ansible/playbooks/prepare.yml` against all remote hosts in the inventory (localhost is excluded). It installs exactly what is required for lean-quickstart ansible deployments: + +| Tool | Why it is needed | +|---|---| +| `python3` | Ansible requires Python on managed nodes — cannot self-bootstrap | +| Docker CE + `docker-compose-plugin` | Every node client and observability container runs via Docker | +| `yq` | The `common` role hard-fails at every deploy if `yq` is absent on the remote | + +After each run, a per-host, per-tool summary is printed. The playbook fails if any tool remains unavailable, so you know immediately which servers need attention. + +Run `--prepare` again at any time — it is fully idempotent. Already-installed tools are skipped; only missing ones trigger installation. + ### Remote Deployment The Ansible inventory is **automatically generated** from `validator-config.yaml`. @@ -843,7 +899,7 @@ The inventory generator will automatically: - Use `--useRoot` flag to connect as root user (defaults to current user) - Or manually add `ansible_user` and `ansible_ssh_private_key_file` to the generated inventory - Or configure in `ansible/ansible.cfg` (see `private_key_file` option) -- Docker is installed on remote hosts (or use `deployment_mode: binary` in group_vars) +- Required software is installed on remote hosts — run `--prepare` first on fresh servers (see [Bootstrapping remote servers](#bootstrapping-remote-servers)) - Required ports are open (QUIC ports, metrics ports) - Genesis files are accessible (copied or mounted) diff --git a/ansible/playbooks/prepare.yml b/ansible/playbooks/prepare.yml new file mode 100644 index 0000000..4bbb459 --- /dev/null +++ b/ansible/playbooks/prepare.yml @@ -0,0 +1,178 @@ +--- +# Prepare playbook: Verify and install the software required to run lean nodes. +# +# Three things are needed on every remote server: +# +# 1. python3 — Ansible requires Python on managed nodes before any task +# can run. It cannot self-bootstrap this. +# 2. Docker CE — every node client (zeam, ream, qlean, lantern, lighthouse, +# grandine, ethlambda) and the full observability stack run as +# Docker containers. The Compose plugin is required for the +# metrics stack. Docker's systemd service must be running. +# 3. yq — the common role (which runs at every deploy) hard-fails if +# `yq --version` is not available on the remote host. +# +# Everything else is either present on a stock Debian/Ubuntu server or is +# installed automatically as a side-effect of the Docker apt repository setup. +# +# Usage (via spin-node.sh): +# ./spin-node.sh --prepare [--sshKey ~/.ssh/id_ed25519] [--useRoot] +# +# Only runs on remote hosts (localhost is excluded). + +- name: Prepare remote servers + hosts: all:!localhost + gather_facts: yes + + tasks: + + # ────────────────────────────────────────────────────────────────────────── + # 1. python3 — must already be present; Ansible cannot install it itself + # ────────────────────────────────────────────────────────────────────────── + + - name: Check python3 is available + command: python3 --version + register: python3_check + changed_when: false + failed_when: false + + - name: Fail early if python3 is missing + fail: + msg: >- + python3 is not installed on {{ inventory_hostname }}. + Install it manually (e.g. apt install python3) before running --prepare. + when: python3_check.rc != 0 + + # ────────────────────────────────────────────────────────────────────────── + # 2. Docker CE + Compose plugin + # ────────────────────────────────────────────────────────────────────────── + + - name: Check if docker is already installed + command: which docker + register: docker_pre + changed_when: false + failed_when: false + + - name: Install Docker CE (Debian/Ubuntu) + when: + - ansible_os_family == "Debian" + - docker_pre.rc != 0 + block: + - name: Install apt prerequisites for Docker repository + apt: + name: + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + update_cache: yes + become: yes + + - name: Create /etc/apt/keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + become: yes + + - name: Download Docker GPG key + get_url: + url: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg" + dest: /etc/apt/keyrings/docker.asc + mode: '0644' + force: no + become: yes + + - name: Add Docker apt repository + apt_repository: + repo: >- + deb [arch={{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }} + signed-by=/etc/apt/keyrings/docker.asc] + https://download.docker.com/linux/{{ ansible_distribution | lower }} + {{ ansible_distribution_release }} stable + state: present + filename: docker + become: yes + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose-plugin + state: present + update_cache: yes + become: yes + + - name: Ensure Docker service is started and enabled + systemd: + name: docker + state: started + enabled: yes + become: yes + when: ansible_os_family == "Debian" + + # ────────────────────────────────────────────────────────────────────────── + # 3. yq — the common role hard-fails at every deploy if this is absent + # ────────────────────────────────────────────────────────────────────────── + + - name: Check if yq is already installed + command: which yq + register: yq_pre + changed_when: false + failed_when: false + + - name: Install yq binary + when: yq_pre.rc != 0 + block: + - name: Resolve architecture suffix for yq + set_fact: + yq_arch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}" + + - name: Download yq from GitHub releases + get_url: + url: "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_{{ yq_arch }}" + dest: /usr/local/bin/yq + mode: '0755' + force: no + become: yes + + # ────────────────────────────────────────────────────────────────────────── + # 4. Verification summary + # ────────────────────────────────────────────────────────────────────────── + + - name: Verify all required tools + command: "{{ item.cmd }}" + register: verify_results + loop: + - { name: "python3", cmd: "python3 --version" } + - { name: "docker", cmd: "docker --version" } + - { name: "docker compose", cmd: "docker compose version" } + - { name: "yq", cmd: "yq --version" } + changed_when: false + failed_when: false + loop_control: + label: "{{ item.name }}" + + - name: Print per-tool status + debug: + msg: >- + [{{ inventory_hostname }}] + {{ item.item.name }}: + {{ '✅ ' + (item.stdout_lines[0] | default('ok')) if item.rc == 0 + else '❌ NOT AVAILABLE (exit ' + (item.rc | string) + ')' }} + loop: "{{ verify_results.results }}" + loop_control: + label: "{{ item.item.name }}" + + - name: Fail if any required tool is still missing + fail: + msg: >- + Required tool '{{ item.item.name }}' is not available on + {{ inventory_hostname }} after preparation. Check the output above. + loop: "{{ verify_results.results }}" + loop_control: + label: "{{ item.item.name }}" + when: item.rc != 0 diff --git a/parse-env.sh b/parse-env.sh index 694ae44..506b421 100755 --- a/parse-env.sh +++ b/parse-env.sh @@ -104,14 +104,18 @@ while [[ $# -gt 0 ]]; do skipLeanpoint=true shift ;; + --prepare) + prepareMode=true + shift + ;; *) # unknown option shift # past argument ;; esac done -# if no node and no restart-client specified, exit -if [[ ! -n "$node" ]] && [[ ! -n "$restartClient" ]]; +# if no node and no restart-client specified, exit (unless --prepare mode) +if [[ ! -n "$node" ]] && [[ ! -n "$restartClient" ]] && [[ "$prepareMode" != "true" ]]; then echo "no node or restart-client specified, exiting..." exit diff --git a/run-ansible.sh b/run-ansible.sh index d16f246..64c8942 100755 --- a/run-ansible.sh +++ b/run-ansible.sh @@ -138,6 +138,9 @@ EXTRA_VARS="$EXTRA_VARS deployment_mode=$DEFAULT_DEPLOYMENT_MODE" if [ "$action" == "stop" ]; then PLAYBOOK="$ANSIBLE_DIR/playbooks/stop-nodes.yml" ACTION_MSG="stopping nodes" +elif [ "$action" == "prepare" ]; then + PLAYBOOK="$ANSIBLE_DIR/playbooks/prepare.yml" + ACTION_MSG="preparing servers" else PLAYBOOK="$ANSIBLE_DIR/playbooks/site.yml" ACTION_MSG="deploying nodes" @@ -162,6 +165,8 @@ if [ $EXIT_CODE -eq 0 ]; then echo "" if [ "$action" == "stop" ]; then echo "✅ Ansible stop operation completed successfully!" + elif [ "$action" == "prepare" ]; then + echo "✅ Server preparation completed successfully!" else echo "✅ Ansible deployment completed successfully!" fi @@ -169,6 +174,8 @@ else echo "" if [ "$action" == "stop" ]; then echo "❌ Ansible stop operation failed with exit code $EXIT_CODE" + elif [ "$action" == "prepare" ]; then + echo "❌ Server preparation failed with exit code $EXIT_CODE" else echo "❌ Ansible deployment failed with exit code $EXIT_CODE" fi diff --git a/spin-node.sh b/spin-node.sh index f100554..ba56184 100755 --- a/spin-node.sh +++ b/spin-node.sh @@ -68,6 +68,32 @@ if [ "$deployment_mode" == "ansible" ] && ([ "$validatorConfig" == "genesis_boot echo "Using Ansible deployment: configDir=$configDir, validator config=$validator_config_file" fi +# Handle --prepare mode: verify and install required software on all remote servers. +# Must run after deployment_mode is resolved but before genesis setup. +if [ -n "$prepareMode" ] && [ "$prepareMode" == "true" ]; then + if [ "$deployment_mode" != "ansible" ]; then + echo "Error: --prepare can only be used in ansible mode." + echo "Set deployment_mode: ansible in your validator-config.yaml or pass --deploymentMode ansible" + exit 1 + fi + + if ! command -v ansible-playbook &> /dev/null; then + echo "Error: ansible-playbook is not installed." + echo "Install Ansible: brew install ansible (macOS) or pip install ansible" + exit 1 + fi + + echo "Preparing remote servers (verifying and installing required software)..." + + if ! "$scriptDir/run-ansible.sh" "$configDir" "" "" "" "$validator_config_file" "$sshKeyFile" "$useRoot" "prepare" "" "" ""; then + echo "❌ Server preparation failed." + exit 1 + fi + + echo "✅ All remote servers are prepared." + exit 0 +fi + #1. setup genesis params and run genesis generator source "$(dirname $0)/set-up.sh" # ✅ Genesis generator implemented using PK's eth-beacon-genesis tool From 1cc7ee231ccd680d497a63fde1130bb6eadd8271 Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Mon, 16 Mar 2026 22:39:32 +0000 Subject: [PATCH 2/4] ansible: open and persist firewall ports in --prepare Extends the prepare playbook to configure ufw on each remote server: - Reads quicPort (UDP), metricsPort (TCP), and apiPort/httpPort (TCP) per-host directly from validator-config.yaml on the Ansible controller, so only the ports actually configured for that node are opened - Opens fixed observability ports on every host: 9090 (prometheus), 9080 (promtail), 9098 (cadvisor), 9100 (node_exporter) - Always allows SSH (22/tcp) before enabling ufw to prevent lockout - Enables ufw with default deny incoming; rules are persisted to disk and survive reboots - Prints ufw status verbose as part of the final summary Also handles Lantern's httpPort field alongside the apiPort field used by all other clients. --- README.md | 30 ++++++-- ansible/playbooks/prepare.yml | 129 ++++++++++++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index acfc02a..75dd609 100644 --- a/README.md +++ b/README.md @@ -209,10 +209,11 @@ Every Ansible deployment automatically deploys an observability stack alongside - Example: Without flag, a random node will be selected automatically 13. `--checkpoint-sync-url` specifies the URL to fetch finalized checkpoint state from for checkpoint sync. Default: `https://leanpoint.leanroadmap.org/lean/v0/states/finalized`. Only used when `--restart-client` is specified. 14. `--restart-client` comma-separated list of client node names (e.g., `zeam_0,ream_0`). When specified, those clients are stopped, their data cleared, and restarted using checkpoint sync. Genesis is skipped. Use with `--checkpoint-sync-url` to override the default URL. -15. `--prepare` verify and install the software required to run lean nodes on every remote server. +15. `--prepare` verify and install the software required to run lean nodes on every remote server, and open + persist the necessary firewall ports. - **Ansible mode only** — fails with an error if `deployment_mode` is not `ansible` - Installs: `python3` (Ansible requirement), Docker CE + Compose plugin (all clients run as containers), `yq` (required by the `common` role at every deploy) - - Prints a per-tool, per-host status summary (`✅ ok` / `❌ missing`) + - Opens per-node ports (`quicPort`/UDP, `metricsPort`/TCP, `apiPort`/TCP) read from `validator-config.yaml`, plus fixed observability ports (9090, 9080, 9098, 9100). Enables `ufw` with default deny incoming (persisted across reboots). + - Prints a per-tool, per-host status summary (`✅ ok` / `❌ missing`) and `ufw status verbose` - `--node` is not required and is ignored; all other flags are also ignored except `--sshKey` and `--useRoot` - Example: `NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare --sshKey ~/.ssh/id_ed25519 --useRoot` @@ -831,7 +832,9 @@ Fresh servers need Docker, build tools, and utilities installed before any lean NETWORK_DIR=ansible-devnet ./spin-node.sh --prepare --sshKey ~/.ssh/id_ed25519 --useRoot ``` -The command runs `ansible/playbooks/prepare.yml` against all remote hosts in the inventory (localhost is excluded). It installs exactly what is required for lean-quickstart ansible deployments: +The command runs `ansible/playbooks/prepare.yml` against all remote hosts in the inventory (localhost is excluded). It installs exactly what is required for lean-quickstart ansible deployments and opens the necessary firewall ports: + +**Software installed:** | Tool | Why it is needed | |---|---| @@ -839,9 +842,26 @@ The command runs `ansible/playbooks/prepare.yml` against all remote hosts in the | Docker CE + `docker-compose-plugin` | Every node client and observability container runs via Docker | | `yq` | The `common` role hard-fails at every deploy if `yq` is absent on the remote | -After each run, a per-host, per-tool summary is printed. The playbook fails if any tool remains unavailable, so you know immediately which servers need attention. +**Firewall rules opened (via `ufw`):** + +Each host's ports are read directly from `validator-config.yaml`, so only the ports actually configured for that node are opened: + +| Port | Protocol | Source | +|---|---|---| +| `quicPort` | UDP | Per-node — QUIC/P2P transport (e.g. 9001) | +| `metricsPort` | TCP | Per-node — Prometheus scrape endpoint (e.g. 9095) | +| `apiPort` / `httpPort` | TCP | Per-node — REST API (e.g. 5055) | +| 9090 | TCP | Observability — Prometheus | +| 9080 | TCP | Observability — Promtail | +| 9098 | TCP | Observability — cAdvisor | +| 9100 | TCP | Observability — Node Exporter | +| 22 | TCP | SSH — always allowed before `ufw` is enabled | + +`ufw` is enabled with `default: deny incoming` and rules are written to disk, so they survive reboots. SSH (22/tcp) is explicitly allowed before `ufw` is activated to prevent lockout. + +After each run, a per-host software status summary and the full `ufw status verbose` output are printed. The playbook fails if any required tool is still missing. -Run `--prepare` again at any time — it is fully idempotent. Already-installed tools are skipped; only missing ones trigger installation. +Run `--prepare` again at any time — it is fully idempotent. Already-installed tools and existing firewall rules are skipped. ### Remote Deployment diff --git a/ansible/playbooks/prepare.yml b/ansible/playbooks/prepare.yml index 4bbb459..3311cb7 100644 --- a/ansible/playbooks/prepare.yml +++ b/ansible/playbooks/prepare.yml @@ -1,7 +1,8 @@ --- -# Prepare playbook: Verify and install the software required to run lean nodes. +# Prepare playbook: Verify and install the software required to run lean nodes, +# then open and persist the firewall rules each host needs. # -# Three things are needed on every remote server: +# Three software prerequisites on every remote server: # # 1. python3 — Ansible requires Python on managed nodes before any task # can run. It cannot self-bootstrap this. @@ -12,8 +13,21 @@ # 3. yq — the common role (which runs at every deploy) hard-fails if # `yq --version` is not available on the remote host. # -# Everything else is either present on a stock Debian/Ubuntu server or is -# installed automatically as a side-effect of the Docker apt repository setup. +# Firewall rules opened per host (read from validator-config.yaml): +# +# - quicPort (UDP) — QUIC/P2P transport for inter-node communication +# - metricsPort (TCP) — Prometheus metrics scrape endpoint +# - apiPort / httpPort (TCP) — REST API +# +# Observability ports opened on every host (fixed, from observability role defaults): +# +# - 9090/tcp — prometheus +# - 9080/tcp — promtail +# - 9098/tcp — cadvisor +# - 9100/tcp — node_exporter +# +# SSH (22/tcp) is always allowed before ufw is enabled to prevent lockout. +# All rules are persisted by ufw on disk and survive reboots. # # Usage (via spin-node.sh): # ./spin-node.sh --prepare [--sshKey ~/.ssh/id_ed25519] [--useRoot] @@ -23,6 +37,10 @@ - name: Prepare remote servers hosts: all:!localhost gather_facts: yes + vars: + # network_dir is passed as an extra var by run-ansible.sh; fall back to + # ansible-devnet relative to the playbook if running the playbook directly. + _genesis_dir: "{{ network_dir | default(playbook_dir + '/../../ansible-devnet') }}/genesis" tasks: @@ -140,7 +158,98 @@ become: yes # ────────────────────────────────────────────────────────────────────────── - # 4. Verification summary + # 4. Firewall rules (ufw) + # + # Ports are read from the local validator-config.yaml on the Ansible + # controller. Each host's inventory_hostname matches its node name + # (e.g. zeam_0, ream_0), so we select the right validator entry by name. + # + # Lantern uses httpPort instead of apiPort — both are handled via the + # default() chain. + # ────────────────────────────────────────────────────────────────────────── + + - name: Install ufw + apt: + name: ufw + state: present + become: yes + when: ansible_os_family == "Debian" + + - name: Read per-node port configuration from validator-config.yaml + vars: + _vc: "{{ lookup('file', _genesis_dir + '/validator-config.yaml') | from_yaml }}" + _entry: "{{ _vc.validators | selectattr('name', 'equalto', inventory_hostname) | list | first | default({}) }}" + set_fact: + fw_quic_port: "{{ _entry.enrFields.quic | default(9001) | string }}" + fw_metrics_port: "{{ _entry.metricsPort | default(9095) | string }}" + fw_api_port: "{{ _entry.apiPort | default(_entry.httpPort | default(5055)) | string }}" + fw_node_found: "{{ (_entry | length) > 0 }}" + + - name: Warn if node not found in validator-config.yaml + debug: + msg: >- + Warning: {{ inventory_hostname }} not found in validator-config.yaml. + Node-specific firewall rules will be skipped. + when: not fw_node_found | bool + + # SSH must be allowed before enabling ufw, or we lock ourselves out. + - name: Allow SSH (22/tcp) + ufw: + rule: allow + port: '22' + proto: tcp + comment: "SSH" + become: yes + + - name: Open QUIC P2P port (UDP) + ufw: + rule: allow + port: "{{ fw_quic_port }}" + proto: udp + comment: "lean-quickstart QUIC P2P ({{ inventory_hostname }})" + become: yes + when: fw_node_found | bool + + - name: Open metrics port (TCP) + ufw: + rule: allow + port: "{{ fw_metrics_port }}" + proto: tcp + comment: "lean-quickstart metrics ({{ inventory_hostname }})" + become: yes + when: fw_node_found | bool + + - name: Open API port (TCP) + ufw: + rule: allow + port: "{{ fw_api_port }}" + proto: tcp + comment: "lean-quickstart API ({{ inventory_hostname }})" + become: yes + when: fw_node_found | bool + + - name: Open observability stack ports (TCP) + ufw: + rule: allow + port: "{{ item.port }}" + proto: tcp + comment: "{{ item.name }}" + become: yes + loop: + - { port: "9090", name: "prometheus" } + - { port: "9080", name: "promtail" } + - { port: "9098", name: "cadvisor" } + - { port: "9100", name: "node_exporter" } + + - name: Enable ufw with default deny incoming (persists across reboots) + ufw: + state: enabled + default: deny + direction: incoming + become: yes + + # ────────────────────────────────────────────────────────────────────────── + # 5. Verification summary # ────────────────────────────────────────────────────────────────────────── - name: Verify all required tools @@ -156,6 +265,12 @@ loop_control: label: "{{ item.name }}" + - name: Show firewall rules summary + command: ufw status verbose + register: ufw_status + changed_when: false + become: yes + - name: Print per-tool status debug: msg: >- @@ -167,6 +282,10 @@ loop_control: label: "{{ item.item.name }}" + - name: Print firewall status + debug: + msg: "{{ ufw_status.stdout_lines }}" + - name: Fail if any required tool is still missing fail: msg: >- From 670ea7caa5b8e6dd34a816fba88ba38155159f1b Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Mon, 16 Mar 2026 22:42:08 +0000 Subject: [PATCH 3/4] ansible: error on unsupported flags used with --prepare --- spin-node.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spin-node.sh b/spin-node.sh index ba56184..7e2718a 100755 --- a/spin-node.sh +++ b/spin-node.sh @@ -77,6 +77,31 @@ if [ -n "$prepareMode" ] && [ "$prepareMode" == "true" ]; then exit 1 fi + # Reject flags that have no meaning in prepare mode. + ignored_flags=() + [ -n "$node" ] && ignored_flags+=("--node") + [ -n "$cleanData" ] && ignored_flags+=("--cleanData") + [ -n "$generateGenesis" ] && ignored_flags+=("--generateGenesis") + [ -n "$FORCE_KEYGEN_FLAG" ] && ignored_flags+=("--forceKeyGen") + [ -n "$stopNodes" ] && ignored_flags+=("--stop") + [ -n "$restartClient" ] && ignored_flags+=("--restart-client") + [ -n "$checkpointSyncUrl" ] && ignored_flags+=("--checkpoint-sync-url") + [ -n "$dockerTag" ] && ignored_flags+=("--tag") + [ -n "$aggregatorNode" ] && ignored_flags+=("--aggregator") + [ -n "$coreDumps" ] && ignored_flags+=("--coreDumps") + [ -n "$enableMetrics" ] && ignored_flags+=("--metrics") + [ -n "$popupTerminal" ] && ignored_flags+=("--popupTerminal") + [ -n "$dockerWithSudo" ] && ignored_flags+=("--dockerWithSudo") + [ -n "$skipLeanpoint" ] && ignored_flags+=("--skip-leanpoint") + [ -n "$validatorConfig" ] && [ "$validatorConfig" != "genesis_bootnode" ] \ + && ignored_flags+=("--validatorConfig") + + if [ ${#ignored_flags[@]} -gt 0 ]; then + echo "Error: --prepare does not accept the following flag(s): ${ignored_flags[*]}" + echo "Only --sshKey (or --private-key), --useRoot, and --deploymentMode are allowed with --prepare." + exit 1 + fi + if ! command -v ansible-playbook &> /dev/null; then echo "Error: ansible-playbook is not installed." echo "Install Ansible: brew install ansible (macOS) or pip install ansible" From d42e61b0eaaedfb7335401247e46aec0c9da122f Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Mon, 16 Mar 2026 22:42:29 +0000 Subject: [PATCH 4/4] ansible: make --prepare flag error message prominent --- spin-node.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/spin-node.sh b/spin-node.sh index 7e2718a..39963e0 100755 --- a/spin-node.sh +++ b/spin-node.sh @@ -97,8 +97,21 @@ if [ -n "$prepareMode" ] && [ "$prepareMode" == "true" ]; then && ignored_flags+=("--validatorConfig") if [ ${#ignored_flags[@]} -gt 0 ]; then - echo "Error: --prepare does not accept the following flag(s): ${ignored_flags[*]}" - echo "Only --sshKey (or --private-key), --useRoot, and --deploymentMode are allowed with --prepare." + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + echo "║ ❌ ERROR ║" + echo "╠══════════════════════════════════════════════════════════════╣" + echo "║ --prepare does not accept the following flag(s): ║" + for flag in "${ignored_flags[@]}"; do + printf "║ %-60s║\n" "• $flag" + done + echo "╠══════════════════════════════════════════════════════════════╣" + echo "║ Allowed flags with --prepare: ║" + echo "║ • --sshKey / --private-key ║" + echo "║ • --useRoot ║" + echo "║ • --deploymentMode ansible ║" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" exit 1 fi