# Lethean Exit Node # Run a VPN exit node and earn LTHN by providing bandwidth to the network. # Peers with Lethean gateways which route encrypted VPN traffic through your node. # # Usage: # cp .env.example .env # # Set WALLET_PASSWORD, EXIT_PUBLIC_IP, and EXIT_NAME # docker compose -f docker-compose.exit.yml up -d # # What it does: # - Runs a full chain node (syncs blockchain) # - Runs a wallet with PoS staking # - Runs a WireGuard VPN server # - Registers as an exit node on-chain via alias # - Peers with gateway nodes for traffic routing # - Earns LTHN for bandwidth provided # # Requirements: # - Public IP address (or port forwarding on router) # - Open ports: 46942 (P2P), 51820/udp (WireGuard) # - Linux with Docker (WireGuard kernel module) # # Ports: # 46942 — P2P (chain peering) # 51820 — WireGuard VPN (UDP) # 8124 — Exit node management API (local only) services: # --- Chain Node --- daemon: image: lthn/chain:testnet container_name: lthn-exit-daemon restart: unless-stopped ports: - "${DAEMON_P2P_PORT:-46942}:36942" volumes: - chain-data:/data entrypoint: - lethean-chain-node command: - --data-dir - /data - --rpc-bind-ip - "0.0.0.0" - --rpc-bind-port - "36941" - --p2p-bind-port - "36942" - --rpc-enable-admin-api - --allow-local-ip - --log-level - "${DAEMON_LOG_LEVEL:-1}" - --disable-upnp - --rpc-ignore-offline healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:36941/json_rpc -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getinfo\"}' -H 'Content-Type: application/json' | grep -q OK"] interval: 30s timeout: 10s retries: 5 start_period: 60s networks: exit-net: # --- Wallet (PoS staking + payment receipt) --- wallet: image: lthn/chain:testnet container_name: lthn-exit-wallet restart: unless-stopped volumes: - wallet-data:/wallet entrypoint: - sh - -c command: - | if [ ! -f /wallet/exit.wallet ]; then echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/exit.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit; fi; lethean-wallet-cli \ --wallet-file /wallet/exit.wallet \ --password '${WALLET_PASSWORD:-}' \ --daemon-address daemon:36941 \ --rpc-bind-port 36944 \ --rpc-bind-ip 0.0.0.0 \ --do-pos-mining depends_on: daemon: condition: service_healthy networks: exit-net: # --- WireGuard VPN Exit --- wireguard: image: lscr.io/linuxserver/wireguard:latest container_name: lthn-exit-wireguard restart: unless-stopped cap_add: - NET_ADMIN - SYS_MODULE environment: PUID: 1000 PGID: 1000 TZ: ${TZ:-Europe/London} SERVERURL: ${EXIT_PUBLIC_IP:-auto} SERVERPORT: 51820 PEERS: ${EXIT_MAX_PEERS:-25} PEERDNS: 1.1.1.1,1.0.0.1 INTERNAL_SUBNET: 10.13.13.0 ALLOWEDIPS: 0.0.0.0/0,::/0 LOG_CONFS: "false" ports: - "51820:51820/udp" volumes: - wireguard-config:/config sysctls: - net.ipv4.conf.all.src_valid_mark=1 - net.ipv4.ip_forward=1 networks: exit-net: # --- Exit Node Controller --- # Manages gateway peering, alias registration, and payment verification controller: image: lthn/chain:testnet container_name: lthn-exit-controller restart: unless-stopped volumes: - controller-data:/data entrypoint: - sh - -c command: - | echo "Lethean Exit Node Controller" echo "Waiting for daemon and wallet..." # Wait for daemon until curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' > /dev/null 2>&1; do sleep 5 done echo "Daemon ready" # Wait for wallet until curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json' > /dev/null 2>&1; do sleep 5 done echo "Wallet ready" # Get wallet address ADDR=$$(curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json' | grep -oP '"address":"[^"]+' | cut -d'"' -f4) echo "Exit node wallet: $$ADDR" # Get chain height HEIGHT=$$(curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' | grep -oP '"height":[0-9]+' | cut -d: -f2) echo "Chain height: $$HEIGHT" # Register alias if EXIT_NAME is set and we have balance if [ -n "${EXIT_NAME:-}" ]; then echo "Exit node name: ${EXIT_NAME}" echo "To register on-chain: send 1 LTHN to this wallet, then alias will auto-register" echo "Alias comment: v=lthn1;type=exit;cap=vpn,proxy;ip=${EXIT_PUBLIC_IP:-auto}" fi # Status loop while true; do BALANCE=$$(curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json' | grep -oP '"balance":[0-9]+' | cut -d: -f2) HEIGHT=$$(curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' | grep -oP '"height":[0-9]+' | cut -d: -f2) WG_PEERS=$$(curl -sf http://wireguard:51820 2>/dev/null | wc -l || echo 0) echo "$$(date +%H:%M:%S) height=$$HEIGHT balance=$$(echo "scale=4; $$BALANCE/1000000000000" | bc 2>/dev/null || echo $$BALANCE) peers=$$WG_PEERS" sleep 60 done depends_on: daemon: condition: service_healthy networks: exit-net: networks: exit-net: driver: bridge volumes: chain-data: wallet-data: wireguard-config: controller-data: