commit 87705d5f5153e09cbbfa38747f316dff366b616e Author: Kumi Date: Sat May 25 22:21:05 2024 +0200 feat: Implement Monero wallet Prometheus exporter Introduced a Prometheus exporter for Monero wallets, capable of reporting wallet balance metrics. The implementation includes: - A Python script `balance.py` for fetching and exposing the wallet balance both as a Prometheus metric and a JSON endpoint. - Dependencies required for running the exporter specified in `requirements.txt`. - A basic `.gitignore` to prevent committing unnecessary files. - A `README.md` file detailing usage and setup instructions. This exporter simplifies monitoring Monero wallet balances by integrating with Prometheus, providing users with real-time insights into their wallet's status. It automates the process of starting a `monero-wallet-rpc` process if needed, making it more convenient for users to set up in various environments. By running this in a Prometheus monitored environment, users can track wallet balances over time, set up alerts, and integrate with Grafana for visualization purposes. This feature enhances the operability and observability of Monero wallets within a Prometheus ecosystem, catering to the needs of Monero wallet owners who require continuous monitoring and reporting of their crypto assets. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7df9a6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +venv/ +*.log +*.pyc +__pycache__/ +wallet/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9161ca3 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Monero Balance Exporter + +This is a simple Prometheus exporter for Monero wallets. It uses the `monero-wallet-rpc` to get the balance of a wallet and exports it as a Prometheus metric. + +## Usage + +```bash +MONERO_WALLET_PATH=/path/to/your/wallet MONERO_WALLET_PASSWORD="YourWalletPassword!" python balance.py +``` + +This will start the exporter on port 5000. You can change the port by setting the `PORT` environment variable. + +The script handles starting a `monero-wallet-rpc` process and connecting to it, so ensure that the `MONERO_RPC_PORT` is set if the default of `18083` is already in use, or set `MONERO_SKIP_RPC` to any value to skip starting the `monero-wallet-rpc` process and connect to an existing one. + +## Metrics + +The exporter only exposes one metric: + +- `monero_wallet_balance` - The balance of the wallet in XMR. + +The metrics are exposed on the `/metrics` endpoint. + +## JSON + +The exporter also exposes the balance as a JSON object on the `/balance` endpoint. diff --git a/balance.py b/balance.py new file mode 100644 index 0000000..9a42edd --- /dev/null +++ b/balance.py @@ -0,0 +1,94 @@ +import subprocess +import time +import os +from flask import Flask, jsonify, Response +from prometheus_client import Gauge, generate_latest +from monero.wallet import Wallet +from monero.backends.jsonrpc import JSONRPCWallet + +app = Flask(__name__) + +wallet_path = os.environ.get("MONERO_WALLET_PATH", "wallet/readonly") +rpc_host = os.environ.get("MONERO_RPC_HOST", "localhost") +rpc_port = os.environ.get("MONERO_RPC_PORT", 18083) +daemon_address = os.environ.get( + "MONERO_DAEMON_ADDRESS", "xmr-node.cakewallet.com:18081" +) +wallet_password = os.environ.get("MONERO_WALLET_PASSWORD", "") + +listen_port = os.environ.get("PORT", 5000) +listen_host = os.environ.get("HOST", "localhost") + +skip_wallet_rpc = os.environ.get("MONERO_SKIP_RPC", False) + +def log(msg): + print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}") + + +# Function to start the Monero wallet RPC server +def start_rpc_server(): + rpc_command = [ + "monero-wallet-rpc", + "--wallet-file", + wallet_path, + "--rpc-bind-port", + str(rpc_port), + "--daemon-address", + daemon_address, + "--password", + wallet_password, + "--disable-rpc-login", + ] + return subprocess.Popen(rpc_command) + + +# Function to check if the RPC server is up +def is_rpc_server_up(host, port): + try: + wallet = Wallet(JSONRPCWallet(host=host, port=port)) + wallet.height() # Try to get the current blockchain height, just to check if the RPC server is up + return True + except ConnectionError: + return False + except Exception as e: + print(f"Unexpected error: {e}") + return False + +if not skip_wallet_rpc: + # Start the wallet RPC server using the public node + wallet_rpc_process = start_rpc_server() + + # Wait for the wallet RPC server to be up + while not is_rpc_server_up("localhost", rpc_port): + log("Waiting for the wallet RPC server to start...") + time.sleep(2) + + log("Wallet RPC server is up!") + +# Connect to the Monero wallet RPC server +wallet = Wallet(JSONRPCWallet(host=rpc_host, port=rpc_port)) + +# Prometheus gauge for the balance +balance_gauge = Gauge("monero_wallet_balance", "Current balance of the Monero wallet") + + +@app.route("/balance") +def get_balance(): + balance = wallet.balance() + balance_gauge.set(balance) + return jsonify({"balance": balance}) + + +@app.route("/metrics") +def metrics(): + balance = wallet.balance() + balance_gauge.set(balance) + return Response(generate_latest(), mimetype="text/plain") + + +if __name__ == "__main__": + try: + app.run(host=listen_host, port=listen_port) + finally: + # Ensure the RPC server is terminated when the application exits + wallet_rpc_process.terminate() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c868395 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +monero +pyyaml +prometheus_client +flask \ No newline at end of file