Infrastructure-as-Code for deploying and managing Gnoland validator and sentry nodes with a complete monitoring stack. Uses Ansible for orchestration and Docker for containerization.
Target environment: Ubuntu or Debian servers (tested on Scaleway).
- Architecture
- Prerequisites
- Inventory setup
- Deployment workflow
- Playbook reference
- Tools & scripts
- Variables reference
- Security considerations
- Adding a new validator
- Local environments (devnet & tmkms-lab)
┌──────────────────────────────────────────────────────────────────────┐
│ Validator Node (private VLAN) │
│ │
│ gnoland (Docker) │
│ OTEL Collector → :9464 │
│ Node Exporter → :9100 │
│ Promtail ─────────────────┐ (logs to sentry loki-proxy) │
└────────────┬────────────────┼──────────────────────────────────────┘
│ │
P2P :26656 (private VLAN)
(private VLAN) │
│ │
┌────────────▼────────────────▼──────────────────────────────────────┐
│ Sentry Node (public) │
│ │
│ gnoland (Docker) ◄───── public P2P :26656 │
│ Node Exporter → :9100 │
│ NGINX Exporter → :9113 │
│ NGINX loki-proxy → :80 (relays logs to Loki with Bearer token) │
│ NGINX proxy vhosts: │
│ :9200 → validator:9100 (node_exporter relay) │
│ :9464 → validator:9464 (OTEL relay) │
└────────────┬──────────────────────────────────────────────────────┬──┘
│ │
(private VLAN) HTTPS+Bearer token
│ │
┌────────────▼───────────────────────────────────────────────────────▼──┐
│ Monitoring Server │
│ │
│ Docker Compose: │
│ Loki :3100 (log aggregation, localhost only) │
│ Prometheus :9090 (metrics collection, localhost only) │
│ Grafana :3000 (dashboards) │
│ │
│ NGINX vhosts: │
│ grafana_domain → Grafana :3000 (HTTPS + TLS) │
│ loki_domain → Loki :3100 (HTTPS + Bearer token + IPs) │
└────────────────────────────────────────────────────────────────────────┘
Logs:
- Sentry relay mode: Validator → Sentry loki-proxy (:80, private VLAN, HTTP) → Loki (:3100, HTTPS + token)
- Direct mode: Validator → Loki (:443, HTTPS + token)
Metrics:
- Prometheus scrapes Sentry :9100 (node metrics)
- Prometheus scrapes Sentry :9113 (NGINX metrics)
- Prometheus scrapes Sentry :9200 (validator node metrics via sentry proxy)
- Prometheus scrapes Sentry :9464 (validator OTEL metrics via sentry proxy)
- Ansible >= 2.14
- Python >= 3.10
- Install:
pip install ansible
- Ubuntu 22.04 LTS or Debian 12+
- SSH key-based access as
root - Internet access during deployment (private network isolation comes in a later step)
- A record for
grafana_domainpointing to monitoring server (required for Let's Encrypt) - A record for
loki_domainpointing to monitoring server (required for Let's Encrypt)
all:
vars:
ansible_user: root
children:
betanet:
hosts:
gno-sentry:
ansible_host: 1.2.3.4 # public IP for SSH access
private_ip: 172.16.12.4 # VLAN IP (set after private network activation)
gno-validator:
ansible_host: 1.2.3.5 # public IP for SSH access (during deployment)
private_ip: 172.16.12.2 # VLAN IP (set after private network activation)
monitoring:
hosts:
gno-monitoring:
ansible_host: 1.2.3.6 # public IP for SSH accesscp group_vars/betanet.yml.example group_vars/betanet.yml
cp group_vars/monitoring.yml.example group_vars/monitoring.ymlEdit both files with your deployment-specific values:
- IPs, monikers, domain names
- Bearer tokens (use
ansible-vaultfor encryption) - Seed nodes, peer configurations
Important: Add both files to .gitignore — they contain real IPs and secrets.
Deploy base packages, Docker, firewall, and gnoland binary on both nodes:
# Sentry (with NGINX reverse proxy)
ansible-playbook -i inventory.yaml 1-base_setup.yml -e target=gno-sentry -e install_nginx=true
# Validator
ansible-playbook -i inventory.yaml 1-base_setup.yml -e target=gno-validatorThis installs:
- System packages and shell aliases
- Docker Engine + Docker Compose v2
- Go 1.25.0 and gnoland binary (built from source)
- UFW firewall
- Node Exporter (port 9100)
- NGINX (sentry only)
SSH to each node and initialize blockchain secrets:
ssh root@<sentry-ip>
gnoland secrets init
gnoland secrets get # save the node_id
ssh root@<validator-ip>
gnoland secrets init
gnoland secrets get # save the node_idUse the node IDs in the next steps:
- Validator node ID → add to sentry's
private_peer_idsandvalidator_p2p_ip - Get seed nodes from gnocore → add to sentry's
seeds
ansible-playbook -i inventory.yaml 2-install-sentry-node.yml \
-e private_peer_ids=<validator_node_id> \
-e validator_p2p_ip=<validator_ip>The sentry node will:
- Accept validator as a private peer (hidden from peer exchange)
- Bootstrap from seed nodes
- Expose public P2P on :26656
ansible-playbook -i inventory.yaml 3-install-validator-node.yml \
-e persistent_peers_sentry=<sentry_node_id>@<sentry_ip>:26656The validator will:
- Connect only to the sentry peer
- Operate in sentry mode (peer exchange disabled,
gno_use_sentry=true) - Not expose P2P publicly
ssh root@<validator-ip>
bash /root/check_status.sh gnoland1
ssh root@<sentry-ip>
bash /root/check_status.sh gnoland1
docker logs <container_id>ansible-playbook -i inventory.yaml 4-backup_logs.sh.yamlThis deploys:
backup.shon validator — extracts 24h of logs, compresses, ships to sentry daily at 00:10rotate.shon sentry — enforces 30-day retention on backups- SSH key pair for validator→sentry SCP access (auto-generated)
Deploy Loki, Prometheus, and Grafana on the monitoring server:
# Production (with TLS)
ansible-playbook -i inventory.yaml 5-deploy-monitoring-stack.yaml \
-e @group_vars/monitoring.yml
# Vagrant (HTTP only)
ansible-playbook -i inventory-vagrant.yaml 5-deploy-monitoring-stack.yaml \
-e target=gno-monitoring-test \
-e @group_vars/monitoring.yml \
--skip-tags tlsThis deploys on the monitoring server:
- Docker Compose stack (Loki + Prometheus + Grafana)
- NGINX vhosts with TLS (Grafana + Loki)
- Grafana datasources (auto-provisioned)
And on the sentry:
- NGINX loki-proxy vhost (:80) — accepts logs from validator, injects Bearer token
If you want Prometheus to scrape validator metrics via the sentry (recommended for private VLAN):
ansible-playbook -i inventory.yaml 5b-deploy-validator-proxies.yamlThis requires validator_proxies list in sentry inventory:
gno-sentry:
validator_proxies:
- name: validator-1
private_ip: 172.16.12.2
node_exporter_port: 9200
otel_port: 9464Prometheus will then scrape:
:9200(validator node_exporter via sentry):9464(validator OTEL via sentry)
Option A — Sentry relay mode (recommended for private VLAN):
ansible-playbook -i inventory.yaml 6-deploy-promtail-sentry.yamlLogs flow: Validator → Sentry loki-proxy (:80) → Loki. No Bearer token stored on validator.
Option B — Direct mode (for standalone validators):
ansible-playbook -i inventory.yaml 6-deploy-promtail-direct.yamlLogs flow: Validator → Loki (:443 HTTPS + Bearer token). Token stored on validator at /etc/promtail/config.yml (mode 0600).
After all nodes are running and validated, activate private network isolation:
ansible-playbook -i inventory.yaml 7-setup-private-network.yml \
-e target=gno-sentry -e vlan_id=<your_vlan_id>
ansible-playbook -i inventory.yaml 7-setup-private-network.yml \
-e target=gno-validator -e vlan_id=<your_vlan_id>The vlan_id is provided by your hosting provider (e.g., Scaleway Private Networks console).
Once active, you can disable the validator's internet access while keeping the private VLAN operational:
ssh root@<validator-ip>
# Disable internet (keeps private VLAN active)
ip addr flush dev eno1
# Re-enable if needed (for playbook re-runs)
dhclient eno1See /root/Network_control.md on the validator for detailed network isolation instructions.
Purpose: Prepare base system for gnoland nodes.
Usage:
ansible-playbook -i inventory.yaml 1-base_setup.yml -e target=gno-sentry -e install_nginx=true
ansible-playbook -i inventory.yaml 1-base_setup.yml -e target=gno-validatorRoles applied to all targets:
base_setup— apt packages, shell aliasesdocker— Docker Engine + Compose v2node_exporter— Prometheus exporter (port 9100)ufw— UFW firewall rulesgnoland— Go 1.25.0 + gnoland binary build
Roles applied when install_nginx=true:
nginx— NGINX reverse proxy
Tags: None (applies all roles)
Purpose: Deploy sentry node via Docker Compose.
Prerequisites:
1-base_setup.ymlcompleted on sentry- Secrets initialized manually:
gnoland secrets init && gnoland secrets get - Validator node ID available
Usage:
ansible-playbook -i inventory.yaml 2-install-sentry-node.yml \
-e private_peer_ids=<validator_node_id> \
-e validator_p2p_ip=<validator_ip>Key variables:
private_peer_ids— validator node ID (hidden from peer exchange)validator_p2p_ip— validator's reachable P2P IPseeds— bootstrap seed nodes (from gnocore)
Tags: config, docker, compose
Purpose: Deploy validator node via Docker Compose.
Prerequisites:
1-base_setup.ymlcompleted on validator- Secrets initialized manually:
gnoland secrets init && gnoland secrets get - Sentry node ID and IP available
Usage:
ansible-playbook -i inventory.yaml 3-install-validator-node.yml \
-e persistent_peers_sentry=<sentry_node_id>@<sentry_ip>:26656Key variables:
persistent_peers_sentry— sentry P2P address (node_id@ip:26656)gno_use_sentry—true(sentry mode, default) orfalse(standalone)
Tags: config, docker, compose
Purpose: Deploy log backup and rotation scripts via cron.
Usage:
ansible-playbook -i inventory.yaml 4-backup_logs.sh.yamlWhat it deploys:
- Validator:
backup.sh— extracts 24h of Docker logs, compresses with xz, ships to sentry via SCP daily at 00:10 - Sentry:
rotate.sh— enforces 30-day retention in backup directory
Automation:
- SSH key pair auto-generated on validator and authorized on sentry
- Cron job added to
/etc/cron.d/gnoland-backup
Tags: None (applies all tasks)
Purpose: Deploy Loki, Prometheus, and Grafana with NGINX proxies.
Two-play structure:
Play 1 — Monitoring server:
- Docker Compose (Loki + Prometheus + Grafana)
- NGINX vhosts with Let's Encrypt TLS
- Grafana datasources (auto-provisioned)
- All services listen on localhost only (NGINX handles public access)
Play 2 — Sentry:
- NGINX loki-proxy vhost (:80)
- Accepts logs from validator on private VLAN
- Injects Bearer token before forwarding to Loki (validators never hold the token)
Usage:
# Production (TLS enabled)
ansible-playbook -i inventory.yaml 5-deploy-monitoring-stack.yaml \
-e @group_vars/monitoring.yml
# Vagrant (HTTP only)
ansible-playbook -i inventory-vagrant.yaml 5-deploy-monitoring-stack.yaml \
-e target=gno-monitoring-test \
-e @group_vars/monitoring.yml \
--skip-tags tlsPrerequisites:
group_vars/monitoring.ymlconfigured- DNS records for
loki_domainandgrafana_domain - NGINX installed on sentry (via
1-base_setup.yml -e install_nginx=true)
Tags: stack, config, grafana, proxy, tls
Stack directory: /opt/monitoring/ on monitoring server
Purpose: Deploy metric proxy vhosts on sentry for validator metrics.
Enables Prometheus to access validator metrics via sentry (no direct validator access needed).
Usage:
ansible-playbook -i inventory.yaml 5b-deploy-validator-proxies.yamlPrerequisites:
- NGINX installed on sentry (via
1-base_setup.yml -e install_nginx=true) validator_proxieslist defined ongno-sentryin inventory
Inventory configuration:
gno-sentry:
validator_proxies:
- name: validator-1
private_ip: 172.16.12.2
node_exporter_port: 9200
otel_port: 9464Deployed vhosts:
:9200→ validator :9100 (node metrics):9464→ validator :9464 (OTEL metrics)
UFW rules: Automatically adds rules to allow monitoring IP on proxy ports
Tags: nginx, ufw
Purpose: Deploy Promtail on validator, logs via sentry relay.
Log path: Validator → Sentry loki-proxy (:80, private VLAN, HTTP) → Loki
Benefits:
- Validator has no Bearer token (no secrets to rotate)
- Sentry injects token transparently
- Works over private VLAN (HTTP, no TLS needed on private segment)
Usage:
ansible-playbook -i inventory.yaml 6-deploy-promtail-sentry.yamlFor pre-activated private networks (SSH jump via sentry):
ansible-playbook -i inventory.yaml 6-deploy-promtail-sentry.yaml \
-e gno_network_mode=private \
-e gno_sentry_ip=<sentry_private_ip> \
-e gno_validator_private_ip=<validator_private_ip>Prerequisites:
5-deploy-monitoring-stack.yamlcompleted (loki-proxy running on sentry)sentry_private_ipset ingroup_vars/betanet.yml
Configuration:
promtail_job_name— Loki job label (default: validator moniker)
Tags: None (applies all tasks)
Purpose: Deploy Promtail on validator, logs directly to Loki.
Log path: Validator → Loki (:443 HTTPS + Bearer token) → Loki
Benefits:
- Direct path (no sentry relay needed)
- Simpler setup for standalone validators
Drawbacks:
- Validator must have internet access on port 443
- Bearer token stored on validator (requires rotation policy)
Usage:
ansible-playbook -i inventory.yaml 6-deploy-promtail-direct.yamlPrerequisites:
5-deploy-monitoring-stack.yamlcompleted- Validator has outbound HTTPS access to
loki_domain loki_domainandloki_bearer_tokenset ingroup_vars/betanet.yml
Security note: Bearer token written to /etc/promtail/config.yml (mode 0600, root only). Encrypt the group vars with ansible-vault:
ansible-vault encrypt group_vars/betanet.ymlTags: None (applies all tasks)
Purpose: Activate private VLAN network interfaces on validator and sentry.
Run this after nodes are validated and running.
Usage:
ansible-playbook -i inventory.yaml 7-setup-private-network.yml \
-e target=gno-sentry -e vlan_id=<vlan_id>
ansible-playbook -i inventory.yaml 7-setup-private-network.yml \
-e target=gno-validator -e vlan_id=<vlan_id>Parameters:
target— host or group to configure (required)vlan_id— VLAN ID from hosting provider (e.g., Scaleway)
What it configures:
- VLAN network interface (e.g.,
eth0.1938) - Static IP from
private_ipinventory variable - Persists across reboots
Note: Status is NOT TESTED YET — validate before using in production.
Tags: None (applies all tasks)
Location: /root/check_status.sh (deployed by playbooks)
Purpose: Pre-flight validation before starting gnoland node.
Usage:
bash /root/check_status.sh gnoland1Checks performed:
| Section | Details |
|---|---|
| docker-compose.yml | Validates image, MONIKER, PERSISTENT_PEERS are set. On sentry: also SEEDS, PRIVATE_PEER_IDS. |
| Secrets | Runs gnoland secrets get, verifies output JSON contains node_id, validator_address, p2p_address. |
| Validator state | Checks priv_validator_state.json exists, reports current height and round. |
| Database | Verifies gnoland-data/db and gnoland-data/wal directories exist. |
| Genesis | Checks genesis.json exists, prints SHA256 checksum. |
| Config | Checks config.toml exists. |
Exit codes:
0— all checks passed1— at least one critical check failed
Output:
✅for successful checks❌for failed checks
Run after each deployment step and after any configuration change.
Copy from betanet.yml.example and populate:
| Variable | Default | Description |
|---|---|---|
gno_dir |
gnoland1 |
Working directory under /root/ on all nodes. |
gno_image |
ghcr.io/gnolang/gno/gnoland:chain-test11 |
Gnoland Docker image tag. |
otel_image |
otel/opentelemetry-collector-contrib:latest |
OTEL Collector image. |
moniker_validator |
samourai-crew-1 |
Validator node display name. |
moniker_sentry |
samourai-dev-sentry-1 |
Sentry node display name. |
pex_validator |
"False" |
Peer exchange on validator. Keep False behind sentry. |
seeds |
"" |
Bootstrap seed nodes for sentry (provided by gnocore). |
private_peer_ids |
"" |
Validator node ID (for sentry to hide validator from peer exchange). |
validator_p2p_ip |
"" |
Validator P2P address reachable from sentry. |
persistent_peers_sentry |
"" |
Sentry P2P address for validator (node_id@ip:26656). |
sentry_private_ip |
"" |
Sentry VLAN IP (used by Promtail sentry relay mode). |
promtail_job_name |
{{ moniker_validator }} |
Loki job label for validator logs. |
loki_domain |
"" |
Loki FQDN (used by Promtail direct mode). |
loki_bearer_token |
"" |
Bearer token for Promtail (direct mode only). Use ansible-vault. |
gno_use_sentry |
true |
Deploy validator in sentry mode (true) or standalone (false). |
install_nginx |
false |
Install NGINX (set true for sentry in 1-base_setup.yml). |
container_name |
gnoland1-validator-1 |
Docker container name (used by backup.sh). |
genesis_url |
S3 URL | URL to download genesis.json. |
config_url |
GitHub URL | URL to download config.toml. |
Copy from monitoring.yml.example and populate:
| Variable | Default | Description |
|---|---|---|
loki_version |
3.4.2 |
Loki Docker image version. |
prometheus_version |
3.1.0 |
Prometheus Docker image version. |
grafana_version |
latest |
Grafana Docker image version. |
promtail_version |
3.4.2 |
Promtail binary version (must match loki_version). |
loki_http_port |
3100 |
Loki internal listen port (never exposed directly). |
loki_domain |
"" |
FQDN for Loki NGINX vhost (DNS must point to monitoring server). |
loki_scheme |
https |
Protocol: https for production, http for Vagrant testing. |
grafana_domain |
"" |
FQDN for Grafana NGINX vhost. |
grafana_admin_password |
"" |
Grafana admin password. Use ansible-vault. |
prometheus_http_port |
9090 |
Prometheus internal listen port. |
prometheus_retention |
30d |
Prometheus metrics retention period. |
prometheus_scrape_jobs |
[] |
Scrape target list (auto-configured by playbook). |
letsencrypt_email |
"" |
Email for Let's Encrypt registration and renewal alerts. |
loki_bearer_token |
"" |
Bearer token for NGINX Loki proxy auth. Use ansible-vault. |
loki_allowed_ips |
[] |
Sentry public IPs allowed to push to Loki (for loki-proxy). |
loki_validator_ips |
[] |
Validator VLAN IPs allowed through sentry loki-proxy. |
loki_retention_hours |
1440 |
Log retention period in hours (default: 60 days). |
loki_ingestion_rate_mb |
32 |
Loki ingestion rate limit in MB/s. |
loki_ingestion_burst_size_mb |
64 |
Loki ingestion burst size in MB. |
Required when using 5b-deploy-validator-proxies.yaml:
gno-sentry:
validator_proxies:
- name: validator-1
private_ip: 172.16.12.2
node_exporter_port: 9200
otel_port: 9464
- name: validator-2
private_ip: 172.16.12.3
node_exporter_port: 9201
otel_port: 9465- Never commit
group_vars/betanet.ymlorgroup_vars/monitoring.yml— add to.gitignore - Encrypt all secrets with
ansible-vault:ansible-vault encrypt group_vars/betanet.yml ansible-vault encrypt_string 'token' --name 'loki_bearer_token'
Secrets must be initialized manually on each node before playbook deployment. This is intentional — it ensures you have a copy of the node IDs:
ssh root@<validator-ip>
gnoland secrets init
gnoland secrets get # save the node_id and p2p_addressSecrets are stored in /root/gnoland1/secrets/ and persisted across playbook re-runs.
After private network activation, disable validator's internet access while keeping the private VLAN operational:
ssh root@<validator-ip>
# Disable internet (preserves private VLAN)
ip addr flush dev eno1
# Re-enable (needed for playbook re-runs)
dhclient eno1Full isolation instructions in /root/Network_control.md on the validator.
auth_enabled: falsein Loki config — security relies entirely on NGINX- NGINX layer:
- IP whitelist (only sentry IPs allowed)
- Bearer token required in
Authorization: Bearerheader - Do not expose port 3100 directly to public internet
- Bearer token in plaintext at
/etc/promtail/config.ymlon validator (direct mode only, mode 0600, root only)
- All services (Loki, Prometheus) listen on
127.0.0.1(localhost only) - NGINX handles all public access with TLS and authentication
- UFW restricts metric scraping to monitoring server IP only
- P2P port (:26656) never exposed on validator after private VLAN activation
- Metrics (:9100, :9464) accessed only through sentry proxies
- Logs shipped through sentry relay (sentry injects Bearer token)
- No direct public internet access needed after bootstrapping
Test environment configuration in inventory-vagrant.yaml:
| Host | IP | Role |
|---|---|---|
gno-validator |
192.168.56.10 | Validator node |
gno-sentry |
192.168.56.11 | Sentry node |
gno-monitoring-test |
192.168.56.12 | Monitoring server |
# Start VMs
vagrant up
# Base setup
ansible-playbook -i inventory-vagrant.yaml 1-base_setup.yml -e target=gno-sentry -e install_nginx=true
ansible-playbook -i inventory-vagrant.yaml 1-base_setup.yml -e target=gno-validator
# Manual secrets init (SSH into each VM)
vagrant ssh gno-sentry
gnoland secrets init && gnoland secrets get
vagrant ssh gno-validator
gnoland secrets init && gnoland secrets get
# Deploy nodes (use the node IDs from above)
ansible-playbook -i inventory-vagrant.yaml 2-install-sentry-node.yml -e private_peer_ids=<...> -e validator_p2p_ip=192.168.56.10
ansible-playbook -i inventory-vagrant.yaml 3-install-validator-node.yml -e persistent_peers_sentry=<...>@192.168.56.11:26656
# Deploy monitoring (HTTP only, no TLS)
ansible-playbook -i inventory-vagrant.yaml 5-deploy-monitoring-stack.yaml \
-e target=gno-monitoring-test \
-e @group_vars/monitoring.yml \
--skip-tags tlsAfter 5-deploy-monitoring-stack.yaml, access Grafana at:
http://grafana.192.168.56.12.nip.io
Use nip.io subdomains in monitoring.yml:
grafana_domain: "grafana.192.168.56.12.nip.io"loki_domain: "loki.192.168.56.12.nip.io"loki_scheme: "http"(nothttps)
This section covers what to do when you need to add a second (or Nth) validator to an existing infrastructure that already has a sentry and a monitoring server running.
Add the new validator host under the validators group in inventory.yaml:
validators:
hosts:
gno-validator: # existing validator
ansible_host: 10.0.0.10
private_ip: 10.0.0.10
moniker_validator: samourai-crew-1
...
gno-validator-2: # new validator
ansible_host: 10.0.0.20
private_ip: 10.0.0.20
moniker_validator: samourai-crew-2
gno_network_mode: public
ansible_ssh_private_key_file: /path/to/keyThen add two entries to validator_proxies on the sentry host. Each validator needs one proxy port for node_exporter and one for OTEL — pick ports that don't conflict with existing ones:
sentries:
hosts:
gno-sentry:
...
validator_proxies:
- name: validator-1 # existing
private_ip: 10.0.0.10
node_exporter_port: 9200
otel_port: 9464
- name: validator-2 # new
private_ip: 10.0.0.20
node_exporter_port: 9201
otel_port: 9465In group_vars/monitoring.yml, add two new scrape jobs for the new validator's metrics (routed through the sentry proxies):
prometheus_scrape_jobs:
# existing jobs ...
- job_name: validator-2-node
static_configs:
- targets: ["<sentry_public_ip>:9201"]
labels:
node: validator-2
- job_name: validator-2-otel
static_configs:
- targets: ["<sentry_public_ip>:9465"]
labels:
node: validator-2Run the base setup and install the node binary:
ansible-playbook -i inventory.yaml 1-base_setup.yml -e target=gno-validator-2Then SSH into the new validator and initialise its secrets:
ssh root@<validator-2-ip>
gnoland secrets init
gnoland secrets get # note the node IDUse the sentry's node ID (already running) as the persistent peer:
ansible-playbook -i inventory.yaml 3-install-validator-node.yml \
-e target=gno-validator-2 \
-e persistent_peers_sentry=<sentry_node_id>@<sentry_private_ip>:26656Re-run the proxy playbook on the sentry. It loops over validator_proxies and is idempotent — existing proxies are untouched, new ones are created:
ansible-playbook -i inventory.yaml 5b-deploy-validator-proxies.yamlThis creates two new NGINX vhosts on the sentry (:9201 and :9465) and adds the corresponding UFW rules.
Push the updated scrape config to the monitoring server:
ansible-playbook -i inventory.yaml 5-deploy-monitoring-stack.yaml --tags configPrometheus will reload automatically and start scraping the new validator's metrics.
ansible-playbook -i inventory.yaml 6-deploy-promtail-sentry.yaml \
-e target=gno-validator-2Logs from the new validator will appear in Grafana under the label job: samourai-crew-2 (the value of moniker_validator).
| Step | Playbook / action | Scope |
|---|---|---|
| 1 | Edit inventory.yaml |
Add host + validator_proxies entry |
| 2 | Edit group_vars/monitoring.yml |
Add 2 Prometheus scrape jobs |
| 3 | 1-base_setup.yml -e target=gno-validator-2 |
New validator only |
| 4 | Manual: gnoland secrets init on new validator |
New validator only |
| 5 | 3-install-validator-node.yml -e target=gno-validator-2 |
New validator only |
| 6 | 5b-deploy-validator-proxies.yaml |
Sentry (idempotent) |
| 7 | 5-deploy-monitoring-stack.yaml --tags config |
Monitoring server |
| 8 | 6-deploy-promtail-sentry.yaml -e target=gno-validator-2 |
New validator only |
Two Docker-based sandboxes live alongside the production Ansible workflow. Both
are self-contained, throwaway dev chains that never commit secrets or keys
— everything generated (keys, genesis, .env, state) is gitignored and
regenerated on each setup.
devnet/ is a 3-validator Gno.land devnet run entirely with
Docker (validator/validator2/validator3, plus a 4th identity reserved for the
GovDAO onboarding scenario, a tx-indexer and a gnoweb explorer). Its purpose is
to exercise every gnomonitoring feature end-to-end — block participation
tracking, downtime/halt alerts, the GovDAO watcher, Prometheus metrics and the
Telegram bots — before merging changes to production. It also hosts the
production softsign + Unix-socket tmkms sidecar reference setup.
One-time setup: GNO_REPO_PATH=/path/to/gno ./bootstrap.sh, then
docker compose up -d. See devnet/README.md for the full
workflow, reset procedures, local dev accounts and the scripted test scenarios
(make help).
tmkms-lab/ is a smaller 2-VM experiment that stands up a
single-validator chain (plus one sentry) and externalizes the validator's
consensus signing to tmkms over TCP:
the private consensus key lives on a second VM, not inside the gnoland
container. Its purpose is to understand and validate the tmkms remote-signer
path before rolling it into production.
See tmkms-lab/README.md for the full 2-VM walkthrough
(image requirements, bootstrap, key exchange, verification).