#!/usr/bin/env bash set -euo pipefail echo "MY Network Node setup (interactive)" SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) BASE_DIR=$(cd "$SCRIPT_DIR/.." && pwd) ENV_FILE="$SCRIPT_DIR/.env" PROJECT_EXAMPLE="$BASE_DIR/.env.example" if [[ ! -f "$SCRIPT_DIR/.env.example" && -f "$PROJECT_EXAMPLE" ]]; then cp "$PROJECT_EXAMPLE" "$SCRIPT_DIR/.env.example" fi EXAMPLE_FILE="$SCRIPT_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" rm -f "$ENV_FILE.bak" else echo "${key}=${value}" >> "$ENV_FILE" fi } ensure_env_default() { local key=$1 default=$2 local cur cur=$(ini_val "$key") if [[ -z "$cur" ]]; then update_env "$key" "$default" 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" existing_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 existing_bootstrap_required=${existing_bootstrap_required:-1} update_env BOOTSTRAP_REQUIRED "$existing_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" ensure_env_default POSTGRES_DB "mysdb" ensure_env_default POSTGRES_USER "service" ensure_env_default POSTGRES_PASSWORD "changeme" ensure_env_default POSTGRES_HOST "db" ensure_env_default POSTGRES_PORT "5432" ensure_env_default POSTGRES_FORWARD_PORT "13580" pg_user=$(ini_val POSTGRES_USER) pg_pass=$(ini_val POSTGRES_PASSWORD) pg_host=$(ini_val POSTGRES_HOST) pg_port=$(ini_val POSTGRES_PORT) pg_db=$(ini_val POSTGRES_DB) update_env DATABASE_URL "postgresql+psycopg2://${pg_user}:${pg_pass}@${pg_host}:${pg_port}/${pg_db}" ensure_env_default FRONTEND_HTTP_PORT "13300" ensure_env_default VITE_SENTRY_DSN "" current_api_base=$(ini_val VITE_API_BASE_URL) if [[ -z "$current_api_base" || "$current_api_base" == "https://my-public-node-8.projscale.dev/api/v1" ]]; then if [[ -n "${PUBLIC_HOST:-}" ]]; then update_env VITE_API_BASE_URL "${PUBLIC_HOST%/}/api/v1" else update_env VITE_API_BASE_URL "http://127.0.0.1:${BACKEND_HTTP_PORT}/api/v1" fi fi current_api_storage=$(ini_val VITE_API_BASE_STORAGE_URL) if [[ -z "$current_api_storage" || "$current_api_storage" == "https://my-public-node-8.projscale.dev/api/v1.5/storage" ]]; then if [[ -n "${PUBLIC_HOST:-}" ]]; then update_env VITE_API_BASE_STORAGE_URL "${PUBLIC_HOST%/}/api/v1.5/storage" else update_env VITE_API_BASE_STORAGE_URL "http://127.0.0.1:${BACKEND_HTTP_PORT}/api/v1.5/storage" fi fi 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="$SCRIPT_DIR/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" <