smoke-firstboot.sh 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env bash
  2. #
  3. # smoke-firstboot.sh — verify the first-boot per-instance credential script.
  4. #
  5. # Installs the released x-ui binary into a container WITHOUT a database, runs
  6. # x-ui-firstboot.sh, and asserts:
  7. # * fresh random credentials are generated (no admin/admin)
  8. # * /etc/x-ui/credentials.txt (600) and /etc/motd are written
  9. # * the sentinel is created and a second run is a no-op (creds unchanged)
  10. #
  11. # Requires Docker and network access. Usage: bash deploy/test/smoke-firstboot.sh
  12. set -euo pipefail
  13. REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
  14. IMAGE="${SMOKE_IMAGE:-ubuntu:24.04}"
  15. if ! command -v docker > /dev/null 2>&1; then
  16. echo "ERROR: docker is required for this smoke test." >&2
  17. exit 1
  18. fi
  19. echo "== first-boot credential smoke test (image: $IMAGE) =="
  20. docker run --rm \
  21. -v "${REPO_ROOT}/deploy/firstboot/x-ui-firstboot.sh:/root/x-ui-firstboot.sh:ro" \
  22. -e DEBIAN_FRONTEND=noninteractive \
  23. "$IMAGE" bash -euo pipefail -c '
  24. apt-get update -qq
  25. apt-get install -y -qq curl tar openssl ca-certificates jq > /dev/null
  26. echo "--- installing released x-ui binary (no DB, no systemd) ---"
  27. REPO=MHSanaei/3x-ui
  28. ARCH=$(dpkg --print-architecture) # amd64 | arm64
  29. echo "container arch: $ARCH"
  30. VER=$(curl --fail --location --silent --show-error \
  31. --retry 5 --retry-all-errors --retry-delay 3 \
  32. --connect-timeout 15 --max-time 60 \
  33. "https://api.github.com/repos/${REPO}/releases/latest" | jq -r .tag_name)
  34. [ -n "$VER" ] && [ "$VER" != "null" ] || { echo "FAIL: cannot resolve version"; exit 1; }
  35. tmp=$(mktemp -d)
  36. # 504s and other transient GitHub/CDN hiccups are retried; a real HTTP
  37. # failure (e.g. missing arch asset) still aborts after the retries.
  38. if ! curl -4 --fail --location --silent --show-error \
  39. --retry 5 --retry-all-errors --retry-delay 3 \
  40. --connect-timeout 15 --max-time 300 \
  41. -o "${tmp}/x.tar.gz" \
  42. "https://github.com/${REPO}/releases/download/${VER}/x-ui-linux-${ARCH}.tar.gz"; then
  43. echo "FAIL: cannot download x-ui-linux-${ARCH}.tar.gz (${VER})" >&2; exit 1
  44. fi
  45. test -s "${tmp}/x.tar.gz" || { echo "FAIL: downloaded tarball is empty"; exit 1; }
  46. tar -xzf "${tmp}/x.tar.gz" -C /usr/local/
  47. chmod +x /usr/local/x-ui/x-ui
  48. install -m 755 /root/x-ui-firstboot.sh /usr/local/x-ui/x-ui-firstboot.sh
  49. # Guarantee a clean slate (the image must never ship a DB).
  50. rm -f /etc/x-ui/x-ui.db /etc/x-ui/.firstboot-done
  51. echo "--- run 1: generate per-instance credentials ---"
  52. /usr/local/x-ui/x-ui-firstboot.sh
  53. test -f /etc/x-ui/.firstboot-done || { echo "FAIL: sentinel not created"; exit 1; }
  54. test -f /etc/x-ui/credentials.txt || { echo "FAIL: credentials.txt missing"; exit 1; }
  55. perms=$(stat -c %a /etc/x-ui/credentials.txt)
  56. [ "$perms" = "600" ] || { echo "FAIL: credentials.txt perms=$perms (want 600)"; exit 1; }
  57. grep -q "3x-ui" /etc/motd || { echo "FAIL: motd not written"; exit 1; }
  58. # shellcheck disable=SC1090
  59. . /etc/x-ui/credentials.txt
  60. [ -n "${XUI_USERNAME:-}" ] && [ "$XUI_USERNAME" != "admin" ] \
  61. || { echo "FAIL: username missing or still admin"; exit 1; }
  62. first_user="$XUI_USERNAME"
  63. /usr/local/x-ui/x-ui setting -show | grep -q "hasDefaultCredential: false" \
  64. || { echo "FAIL: hasDefaultCredential is not false"; exit 1; }
  65. echo "--- run 2: must be a no-op (sentinel honored) ---"
  66. /usr/local/x-ui/x-ui-firstboot.sh
  67. # shellcheck disable=SC1090
  68. . /etc/x-ui/credentials.txt
  69. [ "$XUI_USERNAME" = "$first_user" ] \
  70. || { echo "FAIL: credentials changed on re-run"; exit 1; }
  71. echo "SMOKE_PASS: firstboot user=$first_user (stable across re-run)"
  72. '
  73. echo "== first-boot smoke test PASSED =="