From 91efcd41d000ff20648182980c6bc39c8639cde1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 11:14:30 +0100 Subject: [PATCH] feat(docker): full ecosystem Docker deployment - docker-compose.pull.yml: production compose with pre-built images, health checks, env var config, service dependencies - docker-compose.local.yml: override for mounting existing chain data - docker-compose.ecosystem.yml: build-from-source compose with pool, LNS, Redis added - Chain Dockerfile: add curl for health checks, swagger API resources - Pool Dockerfile: Ubuntu 24.04, Boost 1.83, native ProgPoWZ compilation - .env.example: configurable passwords, ports, hostname - pool-config.json: Docker-networked pool config - health.sh: one-command stack health check - deploy.sh: remote server deployment script - lethean-testnet.service: systemd auto-start unit - README.md: quickstart, mining guide, backup, troubleshooting All 8 services tested and running in Docker: daemon, wallet, explorer, trade-api, trade-frontend, pool, LNS, docs Co-Authored-By: Charon --- .gitignore | 1 + docker/.env.example | 38 ++++ docker/README.md | 134 +++++++++++++ docker/deploy.sh | 66 ++++++ docker/docker-compose.ecosystem.yml | 200 ++++++++++++++++++ docker/docker-compose.local.yml | 7 + docker/docker-compose.pull.yml | 229 +++++++++++++++++++++ docker/health.sh | 58 ++++++ docker/lethean-testnet.service | 15 ++ docker/pool-config.json | 301 ++++++++++++++++++++++++++++ utils/docker/lthn-chain/Dockerfile | 7 +- 11 files changed, 1054 insertions(+), 2 deletions(-) create mode 100644 docker/.env.example create mode 100644 docker/README.md create mode 100755 docker/deploy.sh create mode 100644 docker/docker-compose.ecosystem.yml create mode 100644 docker/docker-compose.local.yml create mode 100644 docker/docker-compose.pull.yml create mode 100755 docker/health.sh create mode 100644 docker/lethean-testnet.service create mode 100644 docker/pool-config.json diff --git a/.gitignore b/.gitignore index 5b3ea01c..22ff8628 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ Thumbs.db .vs/* CMakeUserPresets.json ConanPresets.json +docker/dist/ diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 00000000..ab5ca0fe --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,38 @@ +# Lethean Testnet Configuration +# Copy to .env and customise before running: +# cp .env.example .env +# docker compose -f docker-compose.pull.yml up -d + +# Public hostname (used by explorer and trade frontend) +PUBLIC_HOST=localhost + +# Wallet password (empty = no password, change for production) +WALLET_PASSWORD= + +# Trade API JWT secret (CHANGE THIS for production) +JWT_SECRET=change-me-before-production + +# Explorer database +EXPLORER_DB_USER=explorer +EXPLORER_DB_PASS=explorer +EXPLORER_DB_NAME=lethean_explorer + +# Trade database +TRADE_DB_USER=trade +TRADE_DB_PASS=trade +TRADE_DB_NAME=lethean_trade + +# Daemon log level (0=minimal, 1=normal, 2=detailed, 3=trace) +DAEMON_LOG_LEVEL=1 + +# Port overrides (defaults shown) +# DAEMON_RPC_PORT=46941 +# DAEMON_P2P_PORT=46942 +# WALLET_RPC_PORT=46944 +# EXPLORER_PORT=3335 +# TRADE_API_PORT=3336 +# TRADE_FRONTEND_PORT=3338 +# POOL_STRATUM_PORT=5555 +# POOL_API_PORT=2117 +# LNS_HTTP_PORT=5553 +# LNS_DNS_PORT=5354 diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..d27d9fef --- /dev/null +++ b/docker/README.md @@ -0,0 +1,134 @@ +# Lethean Testnet — Docker Deployment + +Run the full Lethean ecosystem in Docker. No compilation needed. + +## Quick Start + +```bash +# 1. Configure +cp .env.example .env +# Edit .env — at minimum change JWT_SECRET and WALLET_PASSWORD + +# 2. Start +docker compose -f docker-compose.pull.yml up -d + +# 3. Check +docker compose -f docker-compose.pull.yml ps +``` + +The daemon takes ~30 seconds to initialise the chain database on first run. +Other services wait for the daemon health check before starting. + +## Services + +| Service | URL | Description | +|---------|-----|-------------| +| Block Explorer | http://localhost:3335 | Browse blocks, transactions, aliases | +| Trade DEX | http://localhost:3338 | Decentralised exchange frontend | +| Trade API | http://localhost:3336 | REST API for trade operations | +| Mining Pool | http://localhost:2117 | Pool stats API | +| Pool Stratum | localhost:5555 | Connect miners here | +| LNS | http://localhost:5553 | Lethean Name Service HTTP API | +| LNS DNS | localhost:5354 | DNS resolver for .lthn names | +| Daemon RPC | http://localhost:46941 | Chain node JSON-RPC | +| Wallet RPC | http://localhost:46944 | Wallet JSON-RPC | +| Docs | http://localhost:8099 | Documentation site | + +## Mining + +Connect a ProgPoWZ miner to the pool stratum: + +``` +stratum+tcp://YOUR_IP:5555 +``` + +Compatible miners: +- **progminer** (recommended) — `progminer -P stratum+tcp://YOUR_WALLET_ADDRESS@localhost:5555` +- **T-Rex** — ProgPoWZ algorithm +- **TeamRedMiner** — ProgPoWZ algorithm + +## Wallet + +The wallet auto-creates on first start. To check your balance: + +```bash +curl -s http://localhost:46944/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' \ + -H 'Content-Type: application/json' +``` + +The wallet runs with PoS mining enabled (`--do-pos-mining`), so it will stake +any balance automatically. + +## Chain Status + +```bash +curl -s http://localhost:46941/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' \ + -H 'Content-Type: application/json' | python3 -m json.tool +``` + +## Volumes + +| Volume | Contains | Backup? | +|--------|----------|---------| +| `chain-data` | Blockchain database (LMDB) | Optional — can resync | +| `wallet-data` | Wallet file + keys | **Critical — back up regularly** | +| `explorer-db` | Explorer PostgreSQL | Can recreate from chain | +| `trade-db` | Trade PostgreSQL | Important for trade history | + +### Backup wallet + +```bash +docker compose -f docker-compose.pull.yml stop wallet +docker cp lthn-wallet:/wallet ./wallet-backup-$(date +%Y%m%d) +docker compose -f docker-compose.pull.yml start wallet +``` + +## Ports + +All ports can be changed via `.env`. See `.env.example` for the full list. + +If you're running behind a firewall and want external access: +- **P2P:** Open port 46942 (TCP) for other nodes to connect +- **Stratum:** Open port 5555 (TCP) for external miners +- **Explorer:** Open port 3335 (TCP) for public block explorer + +## Troubleshooting + +**Daemon crashes on start:** +Check logs with `docker logs lthn-daemon`. Common causes: +- Port already in use — change ports in `.env` +- Corrupt chain data — remove volume: `docker volume rm docker_chain-data` + +**Wallet shows 0 balance:** +The wallet needs to sync with the daemon first. Check sync status in daemon logs. +New wallets start empty — mine or receive LTHN to fund them. + +**Pool shows "Daemon died":** +The pool needs both the daemon and wallet running. Check both are healthy: +```bash +docker compose -f docker-compose.pull.yml ps daemon wallet +``` + +**Explorer shows "offline":** +The explorer connects to the daemon internally. If the daemon is still syncing, +the explorer will show offline until sync completes. + +## Stopping + +```bash +docker compose -f docker-compose.pull.yml down # stop containers +docker compose -f docker-compose.pull.yml down -v # stop + delete data +``` + +## Building from Source + +If you want to build the images yourself instead of pulling pre-built ones: + +```bash +docker compose -f docker-compose.ecosystem.yml build +docker compose -f docker-compose.ecosystem.yml up -d +``` + +This requires the full source tree — see the main repository README. diff --git a/docker/deploy.sh b/docker/deploy.sh new file mode 100755 index 00000000..82e4a046 --- /dev/null +++ b/docker/deploy.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Lethean Testnet — Production Deploy Script +# Transfers images and config to a remote server and starts the stack. +# +# Usage: +# bash deploy.sh user@server +# bash deploy.sh user@server /opt/lethean/docker +# +# Prerequisites on the remote server: +# - Docker + Docker Compose v2 +# - SSH access + +set -e + +REMOTE="${1:?Usage: deploy.sh user@server [install_dir]}" +INSTALL_DIR="${2:-/opt/lethean/docker}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +IMAGES="$SCRIPT_DIR/dist/lethean-testnet-images.tar.gz" + +if [ ! -f "$IMAGES" ]; then + echo "Building image bundle..." + mkdir -p "$SCRIPT_DIR/dist" + docker save \ + lthn/chain:testnet \ + lthn/explorer:testnet \ + lthn/trade-api:testnet \ + lthn/trade-frontend:testnet \ + lthn/pool:testnet \ + lthn/lns:testnet \ + lthn/docs:testnet \ + | gzip > "$IMAGES" +fi + +echo "Image bundle: $(du -h "$IMAGES" | cut -f1)" +echo "Deploying to $REMOTE:$INSTALL_DIR" +echo "" + +# Create remote directory +ssh "$REMOTE" "mkdir -p $INSTALL_DIR" + +# Transfer files +echo "Transferring config files..." +scp "$SCRIPT_DIR/docker-compose.pull.yml" "$REMOTE:$INSTALL_DIR/" +scp "$SCRIPT_DIR/.env.example" "$REMOTE:$INSTALL_DIR/" +scp "$SCRIPT_DIR/pool-config.json" "$REMOTE:$INSTALL_DIR/" +scp "$SCRIPT_DIR/health.sh" "$REMOTE:$INSTALL_DIR/" +scp "$SCRIPT_DIR/lethean-testnet.service" "$REMOTE:$INSTALL_DIR/" + +# Create .env if it doesn't exist +ssh "$REMOTE" "[ -f $INSTALL_DIR/.env ] || cp $INSTALL_DIR/.env.example $INSTALL_DIR/.env" + +echo "Transferring images (~$(du -h "$IMAGES" | cut -f1))... this may take a while" +scp "$IMAGES" "$REMOTE:/tmp/lethean-testnet-images.tar.gz" + +echo "Loading images on remote..." +ssh "$REMOTE" "docker load < /tmp/lethean-testnet-images.tar.gz && rm /tmp/lethean-testnet-images.tar.gz" + +echo "Starting stack..." +ssh "$REMOTE" "cd $INSTALL_DIR && docker compose -f docker-compose.pull.yml up -d" + +echo "" +echo "Deploy complete. Run health check:" +echo " ssh $REMOTE 'cd $INSTALL_DIR && bash health.sh'" +echo "" +echo "Install systemd service (optional):" +echo " ssh $REMOTE 'sudo cp $INSTALL_DIR/lethean-testnet.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable lethean-testnet'" diff --git a/docker/docker-compose.ecosystem.yml b/docker/docker-compose.ecosystem.yml new file mode 100644 index 00000000..905caa79 --- /dev/null +++ b/docker/docker-compose.ecosystem.yml @@ -0,0 +1,200 @@ +# Lethean Full Ecosystem +# Chain node + wallet + explorer + trade + pool + LNS + docs +# +# Usage: +# docker compose -f docker-compose.ecosystem.yml up -d +# +# URLs after startup: +# Explorer: http://localhost:3335 +# Trade: http://localhost:3337 +# Trade API: http://localhost:3336 +# Pool API: http://localhost:2117 +# Pool Web: http://localhost:8888 +# LNS HTTP: http://localhost:5553 +# LNS DNS: localhost:5354 +# Docs: http://localhost:8098 +# Daemon RPC: http://localhost:46941 +# Wallet RPC: http://localhost:46944 + +services: + # --- Chain --- + daemon: + build: + context: .. + dockerfile: utils/docker/lthn-chain/Dockerfile + target: chain-service + args: + BUILD_TESTNET: 1 + BUILD_THREADS: 4 + container_name: lthn-daemon + ports: + - "46941:36941" + - "46942:36942" + volumes: + - daemon-data:/data + command: > + lethean-chain-node + --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 1 + --disable-upnp + + wallet: + build: + context: .. + dockerfile: utils/docker/lthn-chain/Dockerfile + target: chain-service + args: + BUILD_TESTNET: 1 + BUILD_THREADS: 4 + container_name: lthn-wallet + ports: + - "46944:36944" + volumes: + - wallet-data:/wallet + entrypoint: > + sh -c " + if [ ! -f /wallet/main.wallet ]; then + echo '' | lethean-wallet-cli --generate-new-wallet /wallet/main.wallet --password '' --daemon-address daemon:36941 --command exit; + fi; + lethean-wallet-cli + --wallet-file /wallet/main.wallet + --password '' + --daemon-address daemon:36941 + --rpc-bind-port 36944 + --rpc-bind-ip 0.0.0.0 + --do-pos-mining + " + depends_on: + - daemon + + # --- Explorer --- + explorer-db: + image: postgres:16-alpine + container_name: lthn-explorer-db + environment: + POSTGRES_USER: explorer + POSTGRES_PASSWORD: explorer + POSTGRES_DB: lethean_explorer + volumes: + - explorer-db:/var/lib/postgresql/data + + explorer: + build: + context: ../../zano-upstream/zano-explorer-zarcanum + container_name: lthn-explorer + ports: + - "3335:3335" + environment: + API: http://daemon:36941 + FRONTEND_API: http://localhost:3335 + SERVER_PORT: "3335" + AUDITABLE_WALLET_API: http://daemon:36941 + PGUSER: explorer + PGPASSWORD: explorer + PGDATABASE: lethean_explorer + PGHOST: explorer-db + PGPORT: "5432" + MEXC_API_URL: "" + depends_on: + - daemon + - explorer-db + + # --- Trade --- + trade-db: + image: postgres:16-alpine + container_name: lthn-trade-db + environment: + POSTGRES_USER: trade + POSTGRES_PASSWORD: trade + POSTGRES_DB: lethean_trade + volumes: + - trade-db:/var/lib/postgresql/data + + trade-api: + build: + context: ../../zano-upstream/zano_trade_backend + container_name: lthn-trade-api + ports: + - "3336:3336" + environment: + PORT: "3336" + PGUSER: trade + PGPASSWORD: trade + PGDATABASE: lethean_trade + PGHOST: trade-db + PGPORT: "5432" + JWT_SECRET: testnet-dev-secret + DAEMON_RPC_URL: http://daemon:36941/json_rpc + depends_on: + - daemon + - trade-db + + trade-frontend: + build: + context: ../../zano-upstream/zano_trade_frontend + container_name: lthn-trade-frontend + ports: + - "3337:30289" + environment: + NEXT_PUBLIC_API_URL: http://trade-api:3336 + depends_on: + - trade-api + + # --- Mining Pool --- + pool-redis: + image: redis:7-alpine + container_name: lthn-pool-redis + restart: unless-stopped + + pool: + build: + context: ../.. + dockerfile: zano-upstream/zano-pool/Dockerfile + container_name: lthn-pool + ports: + - "5555:5555" + - "7777:7777" + - "2117:2117" + - "8888:8888" + volumes: + - ./pool-config.json:/pool/config.json:ro + depends_on: + - daemon + - wallet + - pool-redis + + # --- LNS (Lethean Name Service) --- + lns: + build: + context: ../../lns + container_name: lthn-lns + ports: + - "5553:5553" # HTTP API + - "5354:5354/udp" # DNS + - "5354:5354/tcp" # DNS (TCP) + environment: + DAEMON_RPC: http://daemon:36941/json_rpc + LNS_MODE: light + LNS_HTTP_PORT: "5553" + LNS_DNS_PORT: "5354" + depends_on: + - daemon + + # --- Docs --- + docs: + build: + context: ../../zano-upstream/zano-docs + container_name: lthn-docs + ports: + - "8098:80" + +volumes: + daemon-data: + wallet-data: + explorer-db: + trade-db: diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 00000000..37e2a0eb --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,7 @@ +# Local override — mounts existing testnet chain data +# Usage: docker compose -f docker-compose.pull.yml -f docker-compose.local.yml up -d + +services: + daemon: + volumes: + - /opt/lethean/testnet-data:/data diff --git a/docker/docker-compose.pull.yml b/docker/docker-compose.pull.yml new file mode 100644 index 00000000..0f8335c1 --- /dev/null +++ b/docker/docker-compose.pull.yml @@ -0,0 +1,229 @@ +# Lethean Testnet Ecosystem +# Full node + wallet + explorer + trade + pool + LNS +# +# Quick start: +# cp .env.example .env # edit passwords/hostname +# docker compose -f docker-compose.pull.yml up -d +# docker compose -f docker-compose.pull.yml logs -f daemon +# +# Services: +# Explorer: http://${PUBLIC_HOST:-localhost}:3335 +# Trade: http://${PUBLIC_HOST:-localhost}:3338 +# Trade API: http://${PUBLIC_HOST:-localhost}:3336 +# Pool API: http://${PUBLIC_HOST:-localhost}:2117 +# LNS HTTP: http://${PUBLIC_HOST:-localhost}:5553 +# Docs: http://${PUBLIC_HOST:-localhost}:8099 +# Daemon RPC: http://${PUBLIC_HOST:-localhost}:46941 +# Wallet RPC: http://${PUBLIC_HOST:-localhost}:46944 + +services: + # --- Chain --- + daemon: + image: lthn/chain:testnet + container_name: lthn-daemon + restart: unless-stopped + ports: + - "${DAEMON_RPC_PORT:-46941}:36941" + - "${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 + 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: 30s + + wallet: + image: lthn/chain:testnet + container_name: lthn-wallet + restart: unless-stopped + ports: + - "${WALLET_RPC_PORT:-46944}:36944" + volumes: + - wallet-data:/wallet + entrypoint: + - sh + - -c + command: + - | + if [ ! -f /wallet/main.wallet ]; then + echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/main.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit; + fi; + lethean-wallet-cli \ + --wallet-file /wallet/main.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 + + # --- Explorer --- + explorer-db: + image: postgres:16-alpine + container_name: lthn-explorer-db + restart: unless-stopped + environment: + POSTGRES_USER: ${EXPLORER_DB_USER:-explorer} + POSTGRES_PASSWORD: ${EXPLORER_DB_PASS:-explorer} + POSTGRES_DB: ${EXPLORER_DB_NAME:-lethean_explorer} + volumes: + - explorer-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${EXPLORER_DB_USER:-explorer}"] + interval: 10s + timeout: 5s + retries: 5 + + explorer: + image: lthn/explorer:testnet + container_name: lthn-explorer + restart: unless-stopped + ports: + - "${EXPLORER_PORT:-3335}:3335" + environment: + API: http://daemon:36941 + FRONTEND_API: http://${PUBLIC_HOST:-localhost}:${EXPLORER_PORT:-3335} + SERVER_PORT: "3335" + AUDITABLE_WALLET_API: http://daemon:36941 + PGUSER: ${EXPLORER_DB_USER:-explorer} + PGPASSWORD: ${EXPLORER_DB_PASS:-explorer} + PGDATABASE: ${EXPLORER_DB_NAME:-lethean_explorer} + PGHOST: explorer-db + PGPORT: "5432" + MEXC_API_URL: "" + depends_on: + daemon: + condition: service_healthy + explorer-db: + condition: service_healthy + + # --- Trade --- + trade-db: + image: postgres:16-alpine + container_name: lthn-trade-db + restart: unless-stopped + environment: + POSTGRES_USER: ${TRADE_DB_USER:-trade} + POSTGRES_PASSWORD: ${TRADE_DB_PASS:-trade} + POSTGRES_DB: ${TRADE_DB_NAME:-lethean_trade} + volumes: + - trade-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${TRADE_DB_USER:-trade}"] + interval: 10s + timeout: 5s + retries: 5 + + trade-api: + image: lthn/trade-api:testnet + container_name: lthn-trade-api + restart: unless-stopped + ports: + - "${TRADE_API_PORT:-3336}:3336" + environment: + PORT: "3336" + PGUSER: ${TRADE_DB_USER:-trade} + PGPASSWORD: ${TRADE_DB_PASS:-trade} + PGDATABASE: ${TRADE_DB_NAME:-lethean_trade} + PGHOST: trade-db + PGPORT: "5432" + JWT_SECRET: ${JWT_SECRET:-change-me-before-production} + DAEMON_RPC_URL: http://daemon:36941/json_rpc + depends_on: + daemon: + condition: service_healthy + trade-db: + condition: service_healthy + + trade-frontend: + image: lthn/trade-frontend:testnet + container_name: lthn-trade-frontend + restart: unless-stopped + ports: + - "${TRADE_FRONTEND_PORT:-3338}:30289" + environment: + NEXT_PUBLIC_API_URL: http://trade-api:3336 + depends_on: + - trade-api + + # --- Mining Pool --- + pool-redis: + image: redis:7-alpine + container_name: lthn-pool-redis + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + pool: + image: lthn/pool:testnet + container_name: lthn-pool + restart: unless-stopped + ports: + - "${POOL_STRATUM_PORT:-5555}:5555" + - "${POOL_API_PORT:-2117}:2117" + - "7777:7777" + - "8888:8888" + volumes: + - ./pool-config.json:/pool/config.json:ro + depends_on: + daemon: + condition: service_healthy + wallet: + condition: service_started + pool-redis: + condition: service_healthy + + # --- LNS (Lethean Name Service) --- + lns: + image: lthn/lns:testnet + container_name: lthn-lns + restart: unless-stopped + ports: + - "${LNS_HTTP_PORT:-5553}:5553" + - "${LNS_DNS_PORT:-5354}:5354/udp" + - "${LNS_DNS_PORT:-5354}:5354/tcp" + environment: + DAEMON_RPC: http://daemon:36941/json_rpc + LNS_MODE: light + LNS_HTTP_PORT: "5553" + LNS_DNS_PORT: "5354" + depends_on: + daemon: + condition: service_healthy + + # --- Docs --- + docs: + image: lthn/docs:testnet + container_name: lthn-docs + restart: unless-stopped + ports: + - "${DOCS_PORT:-8099}:80" + +volumes: + chain-data: + wallet-data: + explorer-db: + trade-db: diff --git a/docker/health.sh b/docker/health.sh new file mode 100755 index 00000000..dc26685f --- /dev/null +++ b/docker/health.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Lethean Testnet Health Check +# Run: bash health.sh + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +check() { + local name="$1" + local url="$2" + local code + code=$(curl -sf -o /dev/null -w "%{http_code}" "$url" 2>/dev/null) + if [ "$code" = "200" ] || [ "$code" = "307" ] || [ "$code" = "404" ]; then + printf " ${GREEN}%-16s${NC} %s ${YELLOW}(HTTP %s)${NC}\n" "$name" "$url" "$code" + else + printf " ${RED}%-16s${NC} %s ${RED}(DOWN)${NC}\n" "$name" "$url" + fi +} + +rpc() { + local name="$1" + local url="$2" + local method="$3" + local result + result=$(curl -sf "$url" -d "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"$method\"}" -H 'Content-Type: application/json' 2>/dev/null) + if [ -n "$result" ]; then + printf " ${GREEN}%-16s${NC} %s\n" "$name" "$(echo "$result" | python3 -c " +import sys,json +d=json.load(sys.stdin).get('result',{}) +if 'height' in d: + hf = d.get('is_hardfok_active',[]) + active = sum(1 for h in hf if h) + print(f'height={d[\"height\"]}, HF0-{active-1} active, status={d.get(\"status\",\"?\")}') +elif 'balance' in d: + print(f'{d[\"balance\"]/1e12:.4f} LTHN ({d[\"unlocked_balance\"]/1e12:.4f} unlocked)') +else: + print(json.dumps(d)[:80]) +" 2>/dev/null)" + else + printf " ${RED}%-16s${NC} %s ${RED}(DOWN)${NC}\n" "$name" "$url" + fi +} + +echo "" +echo " Lethean Testnet Health Check" +echo " $(date)" +echo " ---" +rpc "Daemon" "http://localhost:${DAEMON_RPC_PORT:-46941}/json_rpc" "getinfo" +rpc "Wallet" "http://localhost:${WALLET_RPC_PORT:-46944}/json_rpc" "getbalance" +check "Explorer" "http://localhost:${EXPLORER_PORT:-3335}/" +check "Trade API" "http://localhost:${TRADE_API_PORT:-3336}/" +check "Trade Web" "http://localhost:${TRADE_FRONTEND_PORT:-3338}/" +check "Pool API" "http://localhost:${POOL_API_PORT:-2117}/stats" +check "LNS" "http://localhost:${LNS_HTTP_PORT:-5553}/" +check "Docs" "http://localhost:${DOCS_PORT:-8099}/" +echo "" diff --git a/docker/lethean-testnet.service b/docker/lethean-testnet.service new file mode 100644 index 00000000..caacdbfe --- /dev/null +++ b/docker/lethean-testnet.service @@ -0,0 +1,15 @@ +[Unit] +Description=Lethean Testnet Ecosystem +Requires=docker.service +After=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/opt/lethean/docker +ExecStart=/usr/bin/docker compose -f docker-compose.pull.yml up -d +ExecStop=/usr/bin/docker compose -f docker-compose.pull.yml down +TimeoutStartSec=120 + +[Install] +WantedBy=multi-user.target diff --git a/docker/pool-config.json b/docker/pool-config.json new file mode 100644 index 00000000..a2e2f446 --- /dev/null +++ b/docker/pool-config.json @@ -0,0 +1,301 @@ +{ + "poolHost": "lethean.somewhere.com", + "coin": "Lethean", + "symbol": "LTHN", + "coinUnits": 1000000000000, + "coinDecimalPlaces": 12, + "coinDifficultyTarget": 120, + "blockchainExplorer": "https://explorer.lthn.io/block/{id}", + "transactionExplorer": "https://explorer.lthn.io/transaction/{id}", + "daemonType": "default", + "cnAlgorithm": "progpowz", + "cnVariant": 2, + "cnBlobType": 0, + "isRandomX": false, + "includeHeight": false, + "previousOffset": 7, + "offset": 2, + "isCryptonight": false, + "reward": 1000000000000, + "logging": { + "files": { + "level": "info", + "directory": "logs", + "flushInterval": 5, + "prefix": "Lethean" + }, + "console": { + "level": "info", + "colors": true + } + }, + "childPools": [], + "poolServer": { + "enabled": true, + "mergedMining": false, + "clusterForks": 3, + "poolAddress": "iTHNUNiuu3VP1yy8xH2y5iQaABKXurdjqZmzFiBiyR4dKG3j6534e9jMriY6SM7PH8NibVwVWW1DWJfQEWnSjS8n3Wgx86pQpY", + "intAddressPrefix": null, + "blockRefreshInterval": 1000, + "minerTimeout": 900, + "sslCert": "cert.pem", + "sslKey": "privkey.pem", + "sslCA": "chain.pem", + "ports": [ + { + "port": 5555, + "difficulty": 50000, + "desc": "Low end hardware" + }, + { + "port": 7777, + "difficulty": 500000, + "desc": "Mid/high end hardware" + }, + { + "port": 8888, + "difficulty": 5000000, + "desc": "Nicehash, MRR" + } + ], + "varDiff": { + "minDiff": 10000, + "maxDiff": 5000000, + "targetTime": 45, + "retargetTime": 60, + "variancePercent": 30, + "maxJump": 100 + }, + "paymentId": { + "addressSeparator": "+" + }, + "fixedDiff": { + "enabled": true, + "addressSeparator": "." + }, + "shareTrust": { + "enabled": true, + "min": 10, + "stepDown": 3, + "threshold": 10, + "penalty": 30 + }, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 25, + "checkThreshold": 30 + }, + "slushMining": { + "enabled": false, + "weight": 300, + "blockTime": 60, + "lastBlockCheckRate": 1 + } + }, + "payments": { + "enabled": true, + "interval": 900, + "maxAddresses": 5, + "mixin": 10, + "priority": 0, + "transferFee": 10000000000, + "dynamicTransferFee": true, + "minerPayFee": true, + "minPayment": 1000000000000, + "maxPayment": 100000000000000, + "maxTransactionAmount": 100000000000000, + "denomination": 1000000000000 + }, + "blockUnlocker": { + "enabled": true, + "interval": 60, + "depth": 10, + "poolFee": 0.2, + "soloFee": 0.2, + "devDonation": 0.5, + "networkFee": 0.0 + }, + "api": { + "enabled": true, + "hashrateWindow": 600, + "updateInterval": 15, + "bindIp": "0.0.0.0", + "port": 2117, + "blocks": 30, + "payments": 30, + "password": "password", + "ssl": false, + "sslPort": 2119, + "sslCert": "cert.pem", + "sslKey": "privkey.pem", + "sslCA": "chain.pem", + "trustProxyIP": true + }, + "zmq": { + "enabled": false, + "host": "127.0.0.1", + "port": 39995 + }, + "daemon": { + "host": "daemon", + "port": 36941 + }, + "wallet": { + "host": "wallet", + "port": 36944 + }, + "redis": { + "host": "pool-redis", + "port": 6379, + "db": 11, + "cleanupInterval": 15 + }, + "notifications": { + "emailTemplate": "email_templates/default.txt", + "emailSubject": { + "emailAdded": "Your email was registered", + "workerConnected": "Worker %WORKER_NAME% connected", + "workerTimeout": "Worker %WORKER_NAME% stopped hashing", + "workerBanned": "Worker %WORKER_NAME% banned", + "blockFound": "Block %HEIGHT% found !", + "blockUnlocked": "Block %HEIGHT% unlocked !", + "blockOrphaned": "Block %HEIGHT% orphaned !", + "payment": "We sent you a payment !" + }, + "emailMessage": { + "emailAdded": "Your email has been registered to receive pool notifications.", + "workerConnected": "Your worker %WORKER_NAME% for address %MINER% is now connected from ip %IP%.", + "workerTimeout": "Your worker %WORKER_NAME% for address %MINER% has stopped submitting hashes on %LAST_HASH%.", + "workerBanned": "Your worker %WORKER_NAME% for address %MINER% has been banned.", + "blockFound": "Block found at height %HEIGHT% by miner %MINER% on %TIME%. Waiting maturity.", + "blockUnlocked": "Block mined at height %HEIGHT% with %REWARD% and %EFFORT% effort on %TIME%.", + "blockOrphaned": "Block orphaned at height %HEIGHT% :(", + "payment": "A payment of %AMOUNT% has been sent to %ADDRESS% wallet." + }, + "telegramMessage": { + "workerConnected": "Your worker _%WORKER_NAME%_ for address _%MINER%_ is now connected from ip _%IP%_.", + "workerTimeout": "Your worker _%WORKER_NAME%_ for address _%MINER%_ has stopped submitting hashes on _%LAST_HASH%_.", + "workerBanned": "Your worker _%WORKER_NAME%_ for address _%MINER%_ has been banned.", + "blockFound": "*Block found at height* _%HEIGHT%_ *by miner* _%MINER%_*! Waiting maturity.*", + "blockUnlocked": "*Block mined at height* _%HEIGHT%_ *with* _%REWARD%_ *and* _%EFFORT%_ *effort on* _%TIME%_*.*", + "blockOrphaned": "*Block orphaned at height* _%HEIGHT%_ *:(*", + "payment": "A payment of _%AMOUNT%_ has been sent." + } + }, + "email": { + "enabled": false, + "fromAddress": "your@email.com", + "transport": "sendmail", + "sendmail": { + "path": "/usr/sbin/sendmail" + }, + "smtp": { + "host": "smtp.example.com", + "port": 587, + "secure": false, + "auth": { + "user": "username", + "pass": "password" + }, + "tls": { + "rejectUnauthorized": false + } + }, + "mailgun": { + "key": "your-private-key", + "domain": "mg.yourdomain" + } + }, + "telegram": { + "enabled": false, + "botName": "", + "token": "", + "channel": "", + "channelStats": { + "enabled": false, + "interval": 30 + }, + "botCommands": { + "stats": "/stats", + "report": "/report", + "notify": "/notify", + "blocks": "/blocks" + } + }, + "monitoring": { + "daemon": { + "checkInterval": 60, + "rpcMethod": "getblockcount" + }, + "wallet": { + "checkInterval": 60, + "rpcMethod": "getbalance" + } + }, + "prices": { + "source": "tradeogre", + "currency": "USD" + }, + "charts": { + "pool": { + "hashrate": { + "enabled": true, + "updateInterval": 60, + "stepInterval": 1800, + "maximumPeriod": 86400 + }, + "miners": { + "enabled": true, + "updateInterval": 60, + "stepInterval": 1800, + "maximumPeriod": 86400 + }, + "workers": { + "enabled": true, + "updateInterval": 60, + "stepInterval": 1800, + "maximumPeriod": 86400 + }, + "difficulty": { + "enabled": true, + "updateInterval": 1800, + "stepInterval": 10800, + "maximumPeriod": 604800 + }, + "price": { + "enabled": true, + "updateInterval": 1800, + "stepInterval": 10800, + "maximumPeriod": 604800 + }, + "profit": { + "enabled": true, + "updateInterval": 1800, + "stepInterval": 10800, + "maximumPeriod": 604800 + } + }, + "user": { + "hashrate": { + "enabled": true, + "updateInterval": 180, + "stepInterval": 1800, + "maximumPeriod": 86400 + }, + "worker_hashrate": { + "enabled": true, + "updateInterval": 60, + "stepInterval": 60, + "maximumPeriod": 86400 + }, + "payments": { + "enabled": true + } + }, + "blocks": { + "enabled": true, + "days": 30 + } + } +} \ No newline at end of file diff --git a/utils/docker/lthn-chain/Dockerfile b/utils/docker/lthn-chain/Dockerfile index 6b7d08a4..26780f3f 100644 --- a/utils/docker/lthn-chain/Dockerfile +++ b/utils/docker/lthn-chain/Dockerfile @@ -58,12 +58,15 @@ RUN if [ "$BUILD_TESTNET" = "1" ]; then \ # use --target=build-artifacts to return the binaries FROM scratch AS build-artifacts -COPY --from=build /code/build/release/src/lethean-* / +COPY --from=build /code/build/release/src/lethean-* /bin/ +COPY --from=build /code/build/release/share/ /share/ # use --target=chain-service to return a working chain node FROM ubuntu:24.04 AS chain-service -COPY --from=build-artifacts --chmod=+x / /bin +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* +COPY --from=build-artifacts --chmod=+x /bin/ /bin/ +COPY --from=build-artifacts /share/ /share/ EXPOSE 36941 EXPOSE 36942