Set up Bitcoin node and Lightning infrastructure with Docker

Create Docker Compose configuration, Bitcoin and LND configuration files, and bootstrap/init scripts for setting up a Bitcoin full node, LND, and LNbits on a Digital Ocean droplet.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0b0f1422-94e9-40dc-9b10-29c5f33a1bac
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
alexpaynex
2026-03-18 18:13:29 +00:00
parent 0921fa1ca3
commit 88b5ebfa3c
6 changed files with 606 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# Bitcoin Core — mainnet
# Placed at /data/bitcoin/bitcoin.conf on the droplet
# Network
server=1
listen=1
bind=0.0.0.0
# RPC (internal Docker network only — never exposed publicly)
rpcbind=0.0.0.0
rpcallowip=172.0.0.0/8
rpcuser=satoshi
# rpcpassword is set by setup.sh — do not hardcode here
# ZMQ — LND uses these to get notified of new blocks/txs
zmqpubrawblock=tcp://0.0.0.0:28332
zmqpubrawrx=tcp://0.0.0.0:28333
# Performance
dbcache=512
maxmempool=300
maxconnections=40
# Prune options — choose one:
# Full node (recommended, ~600GB): comment out both prune lines
# Pruned (~10GB, lower cost but loses historical tx data):
# prune=10240
# Logging
debug=0

View File

@@ -0,0 +1,32 @@
[Application Options]
# Human-readable node alias visible on the Lightning Network
alias=timmy-node
color=#F7931A
# REST API (LNbits connects here — Docker internal only)
restlisten=0.0.0.0:8080
rpclisten=0.0.0.0:10009
# Allow connections from Docker network
tlsextraip=lnd
tlsextradomain=lnd
# Max pending channels
maxpendingchannels=5
[Bitcoin]
bitcoin.active=1
bitcoin.mainnet=1
bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpchost=bitcoin:8332
bitcoind.rpcuser=satoshi
# bitcoind.rpcpass is written by setup.sh at runtime
bitcoind.zmqpubrawblock=tcp://bitcoin:28332
bitcoind.zmqpubrawrx=tcp://bitcoin:28333
[tor]
# Optional: enable Tor for node privacy
# tor.active=1
# tor.v3=1

View File

@@ -0,0 +1,87 @@
services:
bitcoin:
image: lncm/bitcoind:v27.0
container_name: bitcoin
restart: unless-stopped
volumes:
- bitcoin_data:/data/.bitcoin
ports:
- "8333:8333"
networks:
- node-net
healthcheck:
test: ["CMD", "bitcoin-cli", "-conf=/data/.bitcoin/bitcoin.conf", "getblockchaininfo"]
interval: 60s
timeout: 10s
retries: 5
start_period: 30s
lnd:
image: lightningnetwork/lnd:v0.18.3-beta
container_name: lnd
restart: unless-stopped
depends_on:
bitcoin:
condition: service_healthy
volumes:
- lnd_data:/root/.lnd
ports:
- "9735:9735"
networks:
- node-net
healthcheck:
test: ["CMD", "lncli", "--network=mainnet", "state"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s
lnbits:
image: lnbits/lnbits:latest
container_name: lnbits
restart: unless-stopped
depends_on:
lnd:
condition: service_started
ports:
- "127.0.0.1:5000:5000"
volumes:
- lnbits_data:/app/data
- lnd_data:/lnd:ro
environment:
- LNBITS_DATA_FOLDER=/app/data
- LNBITS_BACKEND_WALLET_CLASS=LndRestWallet
- LND_REST_ENDPOINT=https://lnd:8080
- LND_REST_CERT=/lnd/tls.cert
- LND_REST_MACAROON=/lnd/data/chain/bitcoin/mainnet/invoice.macaroon
- LNBITS_SITE_TITLE=Timmy Node
- LNBITS_SITE_TAGLINE=Lightning AI Agent Infrastructure
- UVICORN_HOST=0.0.0.0
- UVICORN_PORT=5000
networks:
- node-net
networks:
node-net:
driver: bridge
volumes:
bitcoin_data:
driver: local
driver_opts:
type: none
o: bind
device: /data/bitcoin
lnd_data:
driver: local
driver_opts:
type: none
o: bind
device: /data/lnd
lnbits_data:
driver: local
driver_opts:
type: none
o: bind
device: /data/lnbits

156
infrastructure/lnd-init.sh Executable file
View File

@@ -0,0 +1,156 @@
#!/usr/bin/env bash
# ============================================================
# LND wallet initialization + LNbits startup
# Run AFTER Bitcoin sync is complete (verificationprogress ~1.0)
# Run as root on the droplet: bash /opt/timmy-node/lnd-init.sh
# ============================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[lnd-init]${NC} $*"; }
ok() { echo -e "${GREEN}[ok]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
die() { echo -e "${RED}[error]${NC} $*"; exit 1; }
INFRA_DIR="/opt/timmy-node"
CREDS_FILE="/root/node-credentials.txt"
# ── Check Bitcoin sync ───────────────────────────────────────
info "Checking Bitcoin sync status..."
PROGRESS=$(docker exec bitcoin bitcoin-cli getblockchaininfo 2>/dev/null | jq -r .verificationprogress || echo "0")
info "Chain sync progress: $PROGRESS"
if (( $(echo "$PROGRESS < 0.999" | bc -l) )); then
warn "Bitcoin is not fully synced yet (progress: $PROGRESS)"
warn "LND needs a synced chain to function correctly."
read -rp "Continue anyway? (y/N) " CONFIRM
[[ "$CONFIRM" != "y" ]] && die "Aborting. Run again when sync is complete."
fi
# ── Start LND ────────────────────────────────────────────────
info "Starting LND..."
cd "$INFRA_DIR"
docker compose up -d lnd
sleep 5
# ── Wallet init ──────────────────────────────────────────────
echo ""
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo -e "${CYAN} LND Wallet Setup${NC}"
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
echo ""
echo -e " Choose one:"
echo -e " ${GREEN}c${NC} — Create a NEW wallet (generates a fresh seed phrase)"
echo -e " ${YELLOW}r${NC} — Restore from existing 24-word seed"
echo ""
read -rp " Your choice [c/r]: " WALLET_CHOICE
if [[ "$WALLET_CHOICE" == "r" ]]; then
info "Restoring wallet from seed..."
docker exec -it lnd lncli --network=mainnet create --recovery-window=2500
else
info "Creating new wallet..."
docker exec -it lnd lncli --network=mainnet create
fi
echo ""
echo -e "${RED}══════════════════════════════════════════════${NC}"
echo -e "${RED} CRITICAL: Write down your 24-word seed phrase${NC}"
echo -e "${RED} It was shown above. Store it offline, safely.${NC}"
echo -e "${RED} This is the ONLY way to recover your funds.${NC}"
echo -e "${RED}══════════════════════════════════════════════${NC}"
echo ""
read -rp " I have written down my seed phrase. Press enter to continue..."
# ── Wait for LND to be ready ─────────────────────────────────
info "Waiting for LND to be ready..."
for i in {1..30}; do
STATE=$(docker exec lnd lncli --network=mainnet state 2>/dev/null | jq -r .state || echo "")
if [[ "$STATE" == "SERVER_ACTIVE" ]]; then
ok "LND is active"
break
fi
echo -n "."
sleep 5
done
echo ""
# ── Get LND pubkey ───────────────────────────────────────────
PUBKEY=$(docker exec lnd lncli --network=mainnet getinfo 2>/dev/null | jq -r .identity_pubkey || echo "")
if [[ -n "$PUBKEY" ]]; then
ok "LND node pubkey: $PUBKEY"
echo "LND_PUBKEY=$PUBKEY" >> "$CREDS_FILE"
fi
# ── Start LNbits ─────────────────────────────────────────────
info "Starting LNbits..."
docker compose up -d lnbits
sleep 8
# ── Wait for LNbits ──────────────────────────────────────────
for i in {1..20}; do
if curl -sf http://127.0.0.1:5000/api/v1/health &>/dev/null; then
ok "LNbits is up"
break
fi
echo -n "."
sleep 3
done
echo ""
# ── Enable Tailscale Funnel for LNbits ───────────────────────
info "Exposing LNbits via Tailscale Funnel (public HTTPS)..."
tailscale serve --bg http://127.0.0.1:5000
tailscale funnel --bg 443
TAILSCALE_HOSTNAME=$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')
LNBITS_URL="https://$TAILSCALE_HOSTNAME"
ok "LNbits is available at: $LNBITS_URL"
echo "LNBITS_URL=$LNBITS_URL" >> "$CREDS_FILE"
# ── Create Timmy wallet in LNbits ────────────────────────────
info "Creating Timmy wallet in LNbits..."
sleep 3
# Create a user + wallet via LNbits API
LNBITS_ADMIN_KEY=$(curl -sf http://127.0.0.1:5000/api/v1/wallets \
-H "Content-Type: application/json" \
| jq -r '.[0].adminkey' 2>/dev/null || echo "")
if [[ -z "$LNBITS_ADMIN_KEY" ]]; then
warn "Could not auto-create wallet. Open $LNBITS_URL in browser,"
warn "create a wallet called 'Timmy', and copy the Invoice Key."
else
# Create a dedicated Timmy wallet
TIMMY_WALLET=$(curl -sf http://127.0.0.1:5000/api/v1/account \
-X POST \
-H "Content-Type: application/json" \
-H "X-Api-Key: $LNBITS_ADMIN_KEY" \
-d '{"name":"Timmy"}' 2>/dev/null || echo "{}")
TIMMY_INVOICE_KEY=$(echo "$TIMMY_WALLET" | jq -r '.wallets[0].inkey' 2>/dev/null || echo "")
if [[ -n "$TIMMY_INVOICE_KEY" ]]; then
ok "Timmy wallet created"
echo "LNBITS_API_KEY=$TIMMY_INVOICE_KEY" >> "$CREDS_FILE"
fi
fi
# ── Final output ─────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
echo -e "${GREEN} LND + LNbits ready — set these in Replit:${NC}"
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
echo ""
grep "LNBITS_URL\|LNBITS_API_KEY" "$CREDS_FILE" 2>/dev/null || true
echo ""
echo -e " If LNBITS_API_KEY is blank above, open $LNBITS_URL,"
echo -e " create a wallet called 'Timmy', and copy its Invoice Key."
echo ""
echo -e " ${CYAN}Next: fund your Lightning node${NC}"
echo -e " Get your on-chain address:"
echo -e " docker exec lnd lncli --network=mainnet newaddress p2wkh"
echo -e " Send at least 0.001 BTC to open your first channel."
echo ""
echo -e " Full credentials: ${YELLOW}cat $CREDS_FILE${NC}"
echo ""

128
infrastructure/ops.sh Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# ============================================================
# Timmy Node — Day-to-day operations helper
# Usage: bash ops.sh <command>
# ============================================================
INFRA_DIR="/opt/timmy-node"
cd "$INFRA_DIR" 2>/dev/null || { echo "Run on the droplet, not locally"; exit 1; }
CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
case "${1:-help}" in
status)
echo -e "\n${CYAN}── Docker services ──────────────────────────${NC}"
docker compose ps
echo -e "\n${CYAN}── Bitcoin sync ──────────────────────────────${NC}"
docker exec bitcoin bitcoin-cli getblockchaininfo 2>/dev/null \
| jq '{blocks, headers, verificationprogress, size_on_disk}' \
|| echo "Bitcoin not ready"
echo -e "\n${CYAN}── LND state ─────────────────────────────────${NC}"
docker exec lnd lncli --network=mainnet getinfo 2>/dev/null \
| jq '{identity_pubkey, alias, num_peers, num_active_channels, synced_to_chain}' \
|| echo "LND not ready"
echo -e "\n${CYAN}── LND wallet balance ────────────────────────${NC}"
docker exec lnd lncli --network=mainnet walletbalance 2>/dev/null \
| jq '{confirmed_balance, unconfirmed_balance}' \
|| true
echo -e "\n${CYAN}── LND channel balance ───────────────────────${NC}"
docker exec lnd lncli --network=mainnet channelbalance 2>/dev/null \
| jq '{balance, pending_open_balance}' \
|| true
;;
sync)
watch -n 10 'docker exec bitcoin bitcoin-cli getblockchaininfo \
| jq "{blocks, verificationprogress, size_on_disk}"'
;;
logs)
SERVICE="${2:-}"
if [[ -z "$SERVICE" ]]; then
docker compose logs -f --tail=50
else
docker compose logs -f --tail=100 "$SERVICE"
fi
;;
restart)
SERVICE="${2:-}"
if [[ -z "$SERVICE" ]]; then
docker compose restart
else
docker compose restart "$SERVICE"
fi
;;
fund)
echo -e "${CYAN}Your on-chain deposit address (send BTC here to fund channels):${NC}"
docker exec lnd lncli --network=mainnet newaddress p2wkh | jq -r .address
;;
channels)
echo -e "\n${CYAN}── Active channels ───────────────────────────${NC}"
docker exec lnd lncli --network=mainnet listchannels 2>/dev/null \
| jq '.channels[] | {remote_pubkey, capacity, local_balance, remote_balance, active}'
echo -e "\n${CYAN}── Pending channels ──────────────────────────${NC}"
docker exec lnd lncli --network=mainnet pendingchannels 2>/dev/null \
| jq '{pending_open_channels: .pending_open_channels | length, pending_closing_channels: .pending_closing_channels | length}'
;;
open-channel)
echo -e "${CYAN}Open a channel — usage:${NC}"
echo " bash ops.sh open-channel <peer_pubkey>@<host>:<port> <amount_sats>"
if [[ -n "${2:-}" && -n "${3:-}" ]]; then
docker exec lnd lncli --network=mainnet connect "$2" 2>/dev/null || true
PUBKEY=$(echo "$2" | cut -d@ -f1)
docker exec lnd lncli --network=mainnet openchannel \
--node_key "$PUBKEY" \
--local_amt "$3" \
--push_amt 0
fi
;;
lnbits-key)
echo -e "${CYAN}LNbits API keys for Timmy wallet:${NC}"
grep "LNBITS" /root/node-credentials.txt 2>/dev/null || \
echo "Check /root/node-credentials.txt — or open LNbits dashboard"
TSHOST=$(tailscale status --json 2>/dev/null | jq -r '.Self.DNSName' | sed 's/\.$//')
echo -e "\nLNbits dashboard: ${GREEN}https://$TSHOST${NC}"
;;
update)
echo -e "${YELLOW}Pulling latest Docker images...${NC}"
docker compose pull
docker compose up -d
ok "Done"
;;
backup)
BACKUP_FILE="/root/lnd-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
echo -e "${CYAN}Backing up LND channel state to $BACKUP_FILE${NC}"
tar -czf "$BACKUP_FILE" -C /data/lnd .
echo -e "${GREEN}Backup saved. Copy this file off the server:${NC}"
echo " scp root@<node>:$BACKUP_FILE ./lnd-backup.tar.gz"
;;
help|*)
echo -e "\n${CYAN}Timmy Node operations:${NC}"
echo ""
echo " bash ops.sh status — overview of all services + balances"
echo " bash ops.sh sync — watch Bitcoin chain sync progress"
echo " bash ops.sh logs [service] — tail logs (bitcoin | lnd | lnbits)"
echo " bash ops.sh restart [svc] — restart a service or all services"
echo " bash ops.sh fund — get on-chain deposit address"
echo " bash ops.sh channels — list open and pending channels"
echo " bash ops.sh open-channel — open a Lightning channel"
echo " bash ops.sh lnbits-key — show LNBITS_URL and API key for Replit"
echo " bash ops.sh update — pull latest Docker images"
echo " bash ops.sh backup — backup LND channel state"
echo ""
;;
esac

173
infrastructure/setup.sh Executable file
View File

@@ -0,0 +1,173 @@
#!/usr/bin/env bash
# ============================================================
# Timmy Bitcoin Node — One-shot bootstrap for Ubuntu 22.04 LTS
# Run as root on a fresh Digital Ocean droplet:
# bash setup.sh
# ============================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[setup]${NC} $*"; }
ok() { echo -e "${GREEN}[ok]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
die() { echo -e "${RED}[error]${NC} $*"; exit 1; }
[[ $EUID -ne 0 ]] && die "Run as root: sudo bash setup.sh"
# ── 0. Config ────────────────────────────────────────────────
DATA_DIR="/data"
INFRA_DIR="/opt/timmy-node"
RPC_PASS=$(openssl rand -hex 24)
info "Starting Timmy node setup..."
echo ""
# ── 1. System packages ───────────────────────────────────────
info "Updating system packages..."
apt-get update -qq
apt-get upgrade -y -qq
apt-get install -y -qq curl wget git ufw jq openssl
# ── 2. Docker ────────────────────────────────────────────────
info "Installing Docker..."
if ! command -v docker &>/dev/null; then
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
fi
docker --version
ok "Docker ready"
# ── 3. Tailscale ─────────────────────────────────────────────
info "Installing Tailscale..."
if ! command -v tailscale &>/dev/null; then
curl -fsSL https://tailscale.com/install.sh | sh
fi
ok "Tailscale installed — you will authenticate it after this script finishes"
# ── 4. Firewall ──────────────────────────────────────────────
info "Configuring UFW firewall..."
ufw --force reset
# Allow Tailscale
ufw allow in on tailscale0
# Bitcoin P2P
ufw allow 8333/tcp comment "Bitcoin P2P"
# Lightning P2P
ufw allow 9735/tcp comment "Lightning P2P"
# SSH via Tailscale only — but keep public SSH open until Tailscale is confirmed
# (after Tailscale auth, you can run: ufw delete allow 22)
ufw allow 22/tcp comment "SSH (disable after Tailscale is confirmed)"
ufw default deny incoming
ufw default allow outgoing
ufw --force enable
ok "Firewall configured"
# ── 5. Mount the DO block volume ─────────────────────────────
info "Setting up data volume..."
# Digital Ocean attaches block volumes at /dev/sda or /dev/disk/by-id/scsi-...
# Find the attached volume (should be the largest unmounted disk)
VOLUME_DEV=$(lsblk -rno NAME,SIZE,MOUNTPOINT | awk '$3=="" && $2~/G/ {print $1}' | grep -v "^sda$" | head -1)
if [[ -z "$VOLUME_DEV" ]]; then
warn "Could not auto-detect a block volume. If you attached a DO Volume,"
warn "run: lsblk to find it, then: mkfs.ext4 /dev/<name> && mount /dev/<name> /data"
warn "Then re-run this script or continue manually."
mkdir -p "$DATA_DIR"
else
VOLUME_PATH="/dev/$VOLUME_DEV"
info "Found volume at $VOLUME_PATH"
# Format only if not already formatted
if ! blkid "$VOLUME_PATH" &>/dev/null; then
info "Formatting $VOLUME_PATH with ext4..."
mkfs.ext4 -F "$VOLUME_PATH"
fi
mkdir -p "$DATA_DIR"
mount "$VOLUME_PATH" "$DATA_DIR"
# Persist mount across reboots
BLKID=$(blkid -s UUID -o value "$VOLUME_PATH")
if ! grep -q "$BLKID" /etc/fstab; then
echo "UUID=$BLKID $DATA_DIR ext4 defaults,nofail 0 2" >> /etc/fstab
fi
ok "Volume mounted at $DATA_DIR"
fi
# ── 6. Directory structure ───────────────────────────────────
info "Creating directory structure..."
mkdir -p \
"$DATA_DIR/bitcoin" \
"$DATA_DIR/lnd" \
"$DATA_DIR/lnbits" \
"$INFRA_DIR/configs"
ok "Directories ready"
# ── 7. Copy configs & inject RPC password ────────────────────
info "Installing configs..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Bitcoin config
cp "$SCRIPT_DIR/configs/bitcoin.conf" "$DATA_DIR/bitcoin/bitcoin.conf"
echo "rpcpassword=$RPC_PASS" >> "$DATA_DIR/bitcoin/bitcoin.conf"
# LND config — inject rpcpass
cp "$SCRIPT_DIR/configs/lnd.conf" "$INFRA_DIR/configs/lnd.conf"
sed -i "s/# bitcoind.rpcpass is written by setup.sh at runtime/bitcoind.rpcpass=$RPC_PASS/" \
"$INFRA_DIR/configs/lnd.conf"
# Mount lnd.conf into container via docker-compose override
cat > "$INFRA_DIR/docker-compose.override.yml" <<OVERRIDE
services:
lnd:
volumes:
- lnd_data:/root/.lnd
- $INFRA_DIR/configs/lnd.conf:/root/.lnd/lnd.conf:ro
OVERRIDE
# Copy main docker-compose
cp "$SCRIPT_DIR/docker-compose.yml" "$INFRA_DIR/docker-compose.yml"
cp "$SCRIPT_DIR/lnd-init.sh" "$INFRA_DIR/lnd-init.sh"
chmod +x "$INFRA_DIR/lnd-init.sh"
ok "Configs installed"
# ── 8. Save credentials ──────────────────────────────────────
CREDS_FILE="/root/node-credentials.txt"
cat > "$CREDS_FILE" <<CREDS
# Timmy Node Credentials — keep this safe
# Generated: $(date -u)
BITCOIN_RPC_USER=satoshi
BITCOIN_RPC_PASS=$RPC_PASS
CREDS
chmod 600 "$CREDS_FILE"
ok "Credentials saved to $CREDS_FILE"
# ── 9. Start Bitcoin Core ────────────────────────────────────
info "Starting Bitcoin Core (this begins chain sync — will take days)..."
cd "$INFRA_DIR"
docker compose up -d bitcoin
ok "Bitcoin Core started"
# ── 10. Done ─────────────────────────────────────────────────
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN} Setup complete — next steps:${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
echo -e " 1. ${CYAN}Authenticate Tailscale:${NC}"
echo -e " tailscale up --ssh"
echo -e " (copy the auth URL, open in browser, log in)"
echo ""
echo -e " 2. ${CYAN}Verify Tailscale SSH works${NC} from another device:"
echo -e " ssh root@<node-name> (via Tailscale MagicDNS)"
echo -e " Then lock down public SSH:"
echo -e " ufw delete allow 22"
echo ""
echo -e " 3. ${CYAN}Check Bitcoin sync progress:${NC}"
echo -e " watch -n 10 'docker exec bitcoin bitcoin-cli getblockchaininfo | jq .verificationprogress'"
echo -e " (will show 0.0 → 1.0 over several days)"
echo ""
echo -e " 4. ${CYAN}Once sync reaches ~1.0, initialize LND:${NC}"
echo -e " bash $INFRA_DIR/lnd-init.sh"
echo ""
echo -e " Credentials: ${YELLOW}cat $CREDS_FILE${NC}"
echo ""