#!/usr/bin/env bash set -euo pipefail echo "MY Network Node setup (interactive)" SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) BASE_DIR="$SCRIPT_DIR/.." ENV_FILE="$BASE_DIR/.env" EXAMPLE_FILE="$BASE_DIR/.env.example" if [[ ! -f "$ENV_FILE" ]]; then if [[ -f "$EXAMPLE_FILE" ]]; then cp "$EXAMPLE_FILE" "$ENV_FILE" else echo "Missing .env.example; cannot continue" >&2 exit 1 fi fi # Safe reader for existing values (does not fail under set -e -o pipefail) ini_val() { local key="$1" local val val=$(awk -F'=' -v k="$key" 'BEGIN{found=0} $1==k{print substr($0, index($0,$2)); found=1} END{ if(!found){} }' "$ENV_FILE" 2>/dev/null || true) echo -n "$val" } read -rp "Node privacy (public/private) [public]: " NODE_PRIVACY NODE_PRIVACY=${NODE_PRIVACY:-public} if [[ "$NODE_PRIVACY" != "public" && "$NODE_PRIVACY" != "private" ]]; then echo "Invalid privacy; defaulting to public" NODE_PRIVACY=public fi # Helper to prompt only if missing in .env ask_or_keep() { local key="$1"; shift local prompt="$1"; shift local def="" if [[ $# -gt 0 ]]; then def="$1" shift fi local invalid="" if [[ $# -gt 0 ]]; then invalid="$1" shift fi local cur cur=$(ini_val "$key") if [[ -n "$cur" && ( -z "$invalid" || "$cur" != "$invalid" ) ]]; then echo "$key is set in .env; keeping existing value" eval "$key=\"$cur\"" return fi local hint="$def" if [[ -n "$hint" ]]; then read -rp "$prompt [${hint}]: " val || true val=${val:-$hint} else read -rp "$prompt: " val || true fi eval "$key=\"$val\"" } if [[ "$NODE_PRIVACY" == "private" ]]; then # For private nodes, PUBLIC_HOST optional; only ask if missing ask_or_keep PUBLIC_HOST "Public host URL (leave empty for private)" "" else ask_or_keep PUBLIC_HOST "Public host URL (e.g., https://node.example.com)" "$(ini_val PUBLIC_HOST)" fi ask_or_keep SANIC_PORT "Internal app port (SANIC_PORT)" "$(ini_val SANIC_PORT)" ask_or_keep BACKEND_HTTP_PORT "Published backend port on host (BACKEND_HTTP_PORT)" "$(ini_val BACKEND_HTTP_PORT)" ask_or_keep BOOTSTRAP_SEEDS "Bootstrap seeds (comma-separated URLs)" "" "https://my-bootstrap-1.example.com,https://my-bootstrap-2.example.com" ask_or_keep HANDSHAKE_INTERVAL_SEC "Handshake interval seconds" "$(ini_val HANDSHAKE_INTERVAL_SEC)" ask_or_keep TELEGRAM_API_KEY "Telegram uploader bot token (TELEGRAM_API_KEY)" "" "YOUR_UPLOADER_BOT_TOKEN" ask_or_keep CLIENT_TELEGRAM_API_KEY "Telegram client bot token (CLIENT_TELEGRAM_API_KEY)" "" "YOUR_CLIENT_BOT_TOKEN" echo "Applying configuration to $ENV_FILE ..." # In-place update helper update_env() { local key=$1 value=$2 if grep -qE "^${key}=" "$ENV_FILE"; then sed -i.bak "s|^${key}=.*$|${key}=${value}|" "$ENV_FILE" else echo "${key}=${value}" >> "$ENV_FILE" fi } update_env NODE_PRIVACY "$NODE_PRIVACY" update_env PUBLIC_HOST "${PUBLIC_HOST:-}" if [[ -n "${PUBLIC_HOST:-}" ]]; then update_env PROJECT_HOST "$PUBLIC_HOST" fi update_env SANIC_PORT "$SANIC_PORT" update_env BACKEND_HTTP_PORT "$BACKEND_HTTP_PORT" update_env BOOTSTRAP_SEEDS "$BOOTSTRAP_SEEDS" local local_bootstrap_required=$(ini_val BOOTSTRAP_REQUIRED) if [[ -z "$BOOTSTRAP_SEEDS" ]]; then echo "No bootstrap seeds provided; disabling mandatory bootstrap." update_env BOOTSTRAP_REQUIRED 0 else local_bootstrap_required=${local_bootstrap_required:-1} update_env BOOTSTRAP_REQUIRED "$local_bootstrap_required" fi update_env HANDSHAKE_INTERVAL_SEC "$HANDSHAKE_INTERVAL_SEC" if [[ -z "$TELEGRAM_API_KEY" || "$TELEGRAM_API_KEY" == "YOUR_UPLOADER_BOT_TOKEN" ]]; then echo "TELEGRAM_API_KEY must be provided (create a bot via @BotFather)." >&2 exit 1 fi if [[ -z "$CLIENT_TELEGRAM_API_KEY" || "$CLIENT_TELEGRAM_API_KEY" == "YOUR_CLIENT_BOT_TOKEN" ]]; then echo "CLIENT_TELEGRAM_API_KEY must be provided (client bot token)." >&2 exit 1 fi update_env TELEGRAM_API_KEY "$TELEGRAM_API_KEY" update_env CLIENT_TELEGRAM_API_KEY "$CLIENT_TELEGRAM_API_KEY" generate_kek() { if command -v openssl >/dev/null 2>&1; then openssl rand -base64 32 | tr -d '\n' elif command -v python3 >/dev/null 2>&1; then python3 - <<'PY' import os, base64 print(base64.b64encode(os.urandom(32)).decode()) PY elif command -v python >/dev/null 2>&1; then python - <<'PY' import os, base64 print(base64.b64encode(os.urandom(32)).decode()) PY else echo "Need openssl or python to generate CONTENT_KEY_KEK_B64" >&2 exit 1 fi } ensure_content_key_kek() { local current current=$(ini_val CONTENT_KEY_KEK_B64) local valid=0 if [[ -n "$current" ]]; then if command -v python3 >/dev/null 2>&1; then if python3 - "$current" <<'PY' >/dev/null 2>&1 import base64, sys try: raw = base64.b64decode(sys.argv[1], validate=False) except Exception: raise SystemExit(1) if len(raw) == 32: raise SystemExit(0) raise SystemExit(1) PY then valid=1 fi elif command -v python >/dev/null 2>&1; then if python - "$current" <<'PY' >/dev/null 2>&1 import base64, sys try: raw = base64.b64decode(sys.argv[1], validate=False) except Exception: raise SystemExit(1) if len(raw) == 32: raise SystemExit(0) raise SystemExit(1) PY then valid=1 fi fi if [[ $valid -eq 1 ]]; then echo "Using existing CONTENT_KEY_KEK_B64 from .env" update_env CONTENT_KEY_KEK_B64 "$current" return else echo "Existing CONTENT_KEY_KEK_B64 is invalid; generating a new key" fi else echo "Generating CONTENT_KEY_KEK_B64 ..." fi local new_kek new_kek=$(generate_kek) update_env CONTENT_KEY_KEK_B64 "$new_kek" } ensure_content_key_kek # Ensure IPFS swarm key exists for private swarm by default SWARM_KEY_FILE_DEFAULT="$BASE_DIR/configs/ipfs/swarm.key" if [[ ! -f "$SWARM_KEY_FILE_DEFAULT" ]]; then echo "Generating IPFS private swarm key at $SWARM_KEY_FILE_DEFAULT ..." mkdir -p "$(dirname "$SWARM_KEY_FILE_DEFAULT")" if command -v openssl >/dev/null 2>&1; then KEYHEX=$(openssl rand -hex 32) else KEYHEX=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n') fi cat > "$SWARM_KEY_FILE_DEFAULT" <