261 lines
8.0 KiB
Bash
261 lines
8.0 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
echo "MY Network Node setup (interactive)"
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
BASE_DIR="$SCRIPT_DIR/.."
|
|
BASE_DIR=$(cd "$BASE_DIR" && pwd)
|
|
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"
|
|
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"
|
|
|
|
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" <<EOF
|
|
/key/swarm/psk/1.0.0/
|
|
/base16/
|
|
$KEYHEX
|
|
EOF
|
|
fi
|
|
update_env IPFS_SWARM_KEY_FILE "$SWARM_KEY_FILE_DEFAULT"
|
|
|
|
# Ensure data directories exist
|
|
mkdir -p "$BASE_DIR/data/ipfs" "$BASE_DIR/data/tusd" "$BASE_DIR/app-logs" "$BASE_DIR/dynamicStorage"
|
|
update_env IPFS_DATA_DIR_HOST "$BASE_DIR/data/ipfs"
|
|
update_env TUSD_DATA_DIR_HOST "$BASE_DIR/data/tusd"
|
|
if ! grep -qE '^IPFS_GATEWAY_BIND=' "$ENV_FILE"; then
|
|
update_env IPFS_GATEWAY_BIND "0.0.0.0"
|
|
fi
|
|
if [[ -n "${PUBLIC_HOST:-}" ]]; then
|
|
update_env VITE_TUS_ENDPOINT "${PUBLIC_HOST%/}/tus/files"
|
|
else
|
|
if ! grep -qE '^VITE_TUS_ENDPOINT=' "$ENV_FILE"; then
|
|
update_env VITE_TUS_ENDPOINT "http://localhost:1080/files"
|
|
fi
|
|
fi
|
|
|
|
echo "Config written to $ENV_FILE. Starting containers..."
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "Docker is required. Please install Docker and retry." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v docker compose >/dev/null 2>&1 && ! command -v docker-compose >/dev/null 2>&1; then
|
|
echo "docker compose (v2) or docker-compose is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
set -x
|
|
COMPOSE_FILE_PATH="$BASE_DIR/configs/docker-compose.yml"
|
|
COMPOSE_PROJECT_RAW=${COMPOSE_PROJECT_NAME:-$(basename "$BASE_DIR")}
|
|
COMPOSE_PROJECT=$(printf '%s' "$COMPOSE_PROJECT_RAW" | tr '[:upper:]' '[:lower:]' | tr -c '[:alnum:]_-' '-')
|
|
while [[ $COMPOSE_PROJECT == -* || $COMPOSE_PROJECT == _* ]]; do
|
|
COMPOSE_PROJECT=${COMPOSE_PROJECT#?}
|
|
done
|
|
if [[ -z $COMPOSE_PROJECT ]]; then
|
|
COMPOSE_PROJECT=mynetwork
|
|
fi
|
|
if [[ $COMPOSE_PROJECT != [a-z0-9]* ]]; then
|
|
COMPOSE_PROJECT="n$COMPOSE_PROJECT"
|
|
fi
|
|
if command -v docker compose >/dev/null 2>&1; then
|
|
docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE_PATH" down --remove-orphans
|
|
docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE_PATH" up -d --build --force-recreate
|
|
else
|
|
docker-compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE_PATH" down --remove-orphans
|
|
docker-compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE_PATH" up -d --build --force-recreate
|
|
fi
|
|
set +x
|
|
|
|
echo "Setup complete. Check health: curl -fsS http://127.0.0.1:${BACKEND_HTTP_PORT}/api/system.version"
|