install.sh 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/bin/sh
  2. # ===========================================================================
  3. # Dis2Hook installer — run INSIDE an existing Alpine Linux LXC, never on the
  4. # Proxmox host itself.
  5. #
  6. # One-liner:
  7. # wget -qO- https://gogs.av2x.dev/av2x/Dis2Hook/raw/master/install.sh | ash
  8. #
  9. # Options via environment:
  10. # D2H_BRANCH=master branch to pull from (auto-falls back to main)
  11. # D2H_DIR=/opt/dis2hook install directory
  12. # D2H_PORT=8823 web UI / webhook listen port
  13. # DISCORD_BOT_TOKEN=... skip the interactive token prompt
  14. # ===========================================================================
  15. set -eu
  16. REPO="${D2H_REPO:-https://gogs.av2x.dev/av2x/Dis2Hook}"
  17. BRANCH="${D2H_BRANCH:-master}"
  18. APP_DIR="${D2H_DIR:-/opt/dis2hook}"
  19. PORT="${D2H_PORT:-8823}"
  20. SERVICE="dis2hook"
  21. say() { printf '\033[1;33m[dis2hook]\033[0m %s\n' "$*"; }
  22. fail() { printf '\033[1;31m[dis2hook] ERROR:\033[0m %s\n' "$*" >&2; exit 1; }
  23. # --- guards ----------------------------------------------------------------
  24. # Never install on the Proxmox host: this belongs inside the container.
  25. if [ -d /etc/pve ] || command -v pveversion >/dev/null 2>&1; then
  26. fail "This looks like a Proxmox VE host. Dis2Hook must be installed INSIDE an Alpine LXC.
  27. Enter the container first, e.g.: pct enter <ctid> then re-run this installer."
  28. fi
  29. [ -f /etc/alpine-release ] || fail "This installer only supports Alpine Linux (no /etc/alpine-release found)."
  30. [ "$(id -u)" = "0" ] || fail "Run as root (Alpine LXC default)."
  31. # --- fetch helper with branch fallback -------------------------------------
  32. fetch() { # fetch <repo-path> <dest>
  33. wget -q -O "$2" "$REPO/raw/$BRANCH/$1" || return 1
  34. [ -s "$2" ]
  35. }
  36. say "Checking repository branch '$BRANCH'…"
  37. TMP="$(mktemp -d)"
  38. trap 'rm -rf "$TMP"' EXIT
  39. if ! fetch app.py "$TMP/app.py"; then
  40. if [ "$BRANCH" = "master" ] && wget -q -O "$TMP/app.py" "$REPO/raw/main/app.py" && [ -s "$TMP/app.py" ]; then
  41. BRANCH="main"
  42. say "Branch 'master' not found, using 'main'."
  43. else
  44. fail "Could not download app.py from $REPO (branch $BRANCH). Check network/DNS inside the container."
  45. fi
  46. fi
  47. grep -q DIS2HOOK_VERSION "$TMP/app.py" || fail "Downloaded app.py does not look valid. Aborting."
  48. # --- packages ---------------------------------------------------------------
  49. say "Installing packages (python3, Flask, requests)…"
  50. apk add --no-cache python3 py3-flask py3-requests >/dev/null
  51. # --- pull application files from the repo -----------------------------------
  52. say "Pulling application files from $REPO ($BRANCH)…"
  53. mkdir -p "$APP_DIR"
  54. fetch index.html "$TMP/index.html" || fail "Could not download index.html."
  55. fetch update.sh "$TMP/update.sh" || fail "Could not download update.sh."
  56. fetch dis2hook.initd "$TMP/dis2hook.initd" || fail "Could not download dis2hook.initd."
  57. grep -qi '<html' "$TMP/index.html" || fail "Downloaded index.html does not look valid. Aborting."
  58. install -m 644 "$TMP/app.py" "$APP_DIR/app.py"
  59. install -m 644 "$TMP/index.html" "$APP_DIR/index.html"
  60. install -m 755 "$TMP/update.sh" "$APP_DIR/update.sh"
  61. install -m 755 "$TMP/dis2hook.initd" "/etc/init.d/$SERVICE"
  62. printf '%s\n' "$BRANCH" > "$APP_DIR/.branch"
  63. # --- secrets: token.json is generated locally, never synced with the repo ---
  64. if [ -f "$APP_DIR/token.json" ]; then
  65. say "Existing token.json found — keeping it (secrets are never overwritten)."
  66. ADMIN_KEY="(unchanged — see $APP_DIR/token.json)"
  67. else
  68. BOT_TOKEN="${DISCORD_BOT_TOKEN:-}"
  69. if [ -z "$BOT_TOKEN" ]; then
  70. say "A Discord bot token is required (Discord Developer Portal → your app → Bot → Token)."
  71. printf '[dis2hook] Paste bot token (input hidden): '
  72. stty -echo < /dev/tty 2>/dev/null || true
  73. read -r BOT_TOKEN < /dev/tty
  74. stty echo < /dev/tty 2>/dev/null || true
  75. printf '\n'
  76. fi
  77. [ -n "$BOT_TOKEN" ] || fail "No bot token provided. Re-run, or set DISCORD_BOT_TOKEN=… before the one-liner."
  78. ADMIN_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(16))')"
  79. umask 077
  80. python3 - "$APP_DIR/token.json" "$BOT_TOKEN" "$ADMIN_KEY" <<'PYEOF'
  81. import json, sys
  82. path, bot, admin = sys.argv[1], sys.argv[2], sys.argv[3]
  83. with open(path, "w") as f:
  84. json.dump({"bot_token": bot, "admin_key": admin}, f, indent=2)
  85. PYEOF
  86. chmod 600 "$APP_DIR/token.json"
  87. umask 022
  88. say "Generated $APP_DIR/token.json (chmod 600)."
  89. fi
  90. # --- default config (only if missing) ----------------------------------------
  91. if [ ! -f "$APP_DIR/config.json" ]; then
  92. cat > "$APP_DIR/config.json" <<CFGEOF
  93. {
  94. "listen_host": "0.0.0.0",
  95. "listen_port": $PORT,
  96. "heartbeat": { "enabled": false, "channel_id": "", "interval_minutes": 60 },
  97. "status": { "enabled": false, "channel_id": "" },
  98. "sources": []
  99. }
  100. CFGEOF
  101. say "Wrote default config.json (port $PORT)."
  102. fi
  103. # --- service ------------------------------------------------------------------
  104. say "Registering OpenRC service '$SERVICE'…"
  105. rc-update add "$SERVICE" default >/dev/null 2>&1 || true
  106. rc-service "$SERVICE" restart >/dev/null 2>&1 || rc-service "$SERVICE" start
  107. sleep 1
  108. IP="$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -n1)"
  109. [ -n "$IP" ] || IP="<container-ip>"
  110. ACTUAL_PORT="$(python3 -c 'import json;print(json.load(open("'"$APP_DIR"'/config.json"))["listen_port"])')"
  111. printf '\n'
  112. say "──────────────────────────────────────────────────────"
  113. say "Dis2Hook is installed and running."
  114. say " Web UI : http://$IP:$ACTUAL_PORT/"
  115. say " Admin key : $ADMIN_KEY"
  116. say " App dir : $APP_DIR"
  117. say " Logs : tail -f /var/log/dis2hook.log"
  118. say " Update : $APP_DIR/update.sh"
  119. say "──────────────────────────────────────────────────────"
  120. say "Keep the admin key safe — it is stored only in token.json."