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 <charon@lethean.io>
This commit is contained in:
parent
b61d773a0f
commit
91efcd41d0
11 changed files with 1054 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ Thumbs.db
|
|||
.vs/*
|
||||
CMakeUserPresets.json
|
||||
ConanPresets.json
|
||||
docker/dist/
|
||||
|
|
|
|||
38
docker/.env.example
Normal file
38
docker/.env.example
Normal file
|
|
@ -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
|
||||
134
docker/README.md
Normal file
134
docker/README.md
Normal file
|
|
@ -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.
|
||||
66
docker/deploy.sh
Executable file
66
docker/deploy.sh
Executable file
|
|
@ -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'"
|
||||
200
docker/docker-compose.ecosystem.yml
Normal file
200
docker/docker-compose.ecosystem.yml
Normal file
|
|
@ -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:
|
||||
7
docker/docker-compose.local.yml
Normal file
7
docker/docker-compose.local.yml
Normal file
|
|
@ -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
|
||||
229
docker/docker-compose.pull.yml
Normal file
229
docker/docker-compose.pull.yml
Normal file
|
|
@ -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:
|
||||
58
docker/health.sh
Executable file
58
docker/health.sh
Executable file
|
|
@ -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 ""
|
||||
15
docker/lethean-testnet.service
Normal file
15
docker/lethean-testnet.service
Normal file
|
|
@ -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
|
||||
301
docker/pool-config.json
Normal file
301
docker/pool-config.json
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue