mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 01:27:11 +00:00
222 lines
9.0 KiB
Bash
222 lines
9.0 KiB
Bash
#!/bin/sh
|
|
# BetterDesk — All-in-One Container Entrypoint
|
|
# Runs Go server + Node.js console via supervisord
|
|
set -e
|
|
|
|
echo "========================================"
|
|
echo " BetterDesk All-in-One Container"
|
|
echo " Version: ${BETTERDESK_IMAGE_VERSION:-3.4.3}"
|
|
echo "========================================"
|
|
echo ""
|
|
echo "Components:"
|
|
echo " Go Server: signal + relay + API (ports 21115-21121)"
|
|
echo " Node.js Console: web panel (port ${PORT:-5000})"
|
|
echo " Client API: port ${API_PORT:-21121}"
|
|
echo ""
|
|
echo "Configuration:"
|
|
echo " NODE_ENV: ${NODE_ENV:-production}"
|
|
echo " DB_TYPE: ${DB_TYPE:-sqlite}"
|
|
echo " ENCRYPTED_ONLY: ${ENCRYPTED_ONLY:-1}"
|
|
echo " DATA_DIR: ${DATA_DIR:-/app/data}"
|
|
echo ""
|
|
|
|
# Public Docker examples use ADMIN_*; seed both the Go API and Node.js console.
|
|
if [ -n "${ADMIN_USERNAME:-}" ]; then
|
|
if [ -z "${INIT_ADMIN_USER:-}" ]; then
|
|
export INIT_ADMIN_USER="$ADMIN_USERNAME"
|
|
fi
|
|
if [ -z "${DEFAULT_ADMIN_USERNAME:-}" ]; then
|
|
export DEFAULT_ADMIN_USERNAME="$ADMIN_USERNAME"
|
|
fi
|
|
fi
|
|
if [ -n "${ADMIN_PASSWORD:-}" ]; then
|
|
if [ -z "${INIT_ADMIN_PASS:-}" ]; then
|
|
export INIT_ADMIN_PASS="$ADMIN_PASSWORD"
|
|
fi
|
|
if [ -z "${DEFAULT_ADMIN_PASSWORD:-}" ]; then
|
|
export DEFAULT_ADMIN_PASSWORD="$ADMIN_PASSWORD"
|
|
fi
|
|
fi
|
|
|
|
# Ensure data directories exist and have correct permissions
|
|
mkdir -p /opt/rustdesk /app/data /var/log/betterdesk 2>/dev/null || true
|
|
chown -R betterdesk:betterdesk /opt/rustdesk /app/data /var/log/betterdesk 2>/dev/null || true
|
|
|
|
# Write a file as betterdesk. Fresh named volumes inherit image ownership
|
|
# (UID 10001); with compose cap_drop:ALL root has no CAP_DAC_OVERRIDE and
|
|
# cannot create files there (Permission denied on .api_key, issue #299).
|
|
write_as_betterdesk() {
|
|
# usage: write_as_betterdesk <path> <content>
|
|
_wad_path="$1"
|
|
_wad_content="$2"
|
|
if command -v su-exec >/dev/null 2>&1; then
|
|
printf '%s\n' "$_wad_content" | su-exec betterdesk sh -c "umask 077; cat > \"$_wad_path\""
|
|
else
|
|
printf '%s\n' "$_wad_content" | su -s /bin/sh betterdesk -c "umask 077; cat > \"$_wad_path\""
|
|
fi
|
|
}
|
|
touch_as_betterdesk() {
|
|
if command -v su-exec >/dev/null 2>&1; then
|
|
su-exec betterdesk touch "$1"
|
|
else
|
|
su -s /bin/sh betterdesk -c "touch \"$1\""
|
|
fi
|
|
}
|
|
|
|
# Bootstrap API key (shared between Go server and Node.js console)
|
|
API_KEY_FILE="/opt/rustdesk/.api_key"
|
|
if [ -z "${API_KEY:-}" ] && [ ! -f "$API_KEY_FILE" ]; then
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
API_KEY=$(openssl rand -hex 32)
|
|
else
|
|
API_KEY=$(cat /dev/urandom | head -c 32 | od -An -tx1 | tr -d ' \n')
|
|
fi
|
|
write_as_betterdesk "$API_KEY_FILE" "$API_KEY"
|
|
echo "Auto-generated API key → $API_KEY_FILE"
|
|
elif [ -n "${API_KEY:-}" ] && [ ! -f "$API_KEY_FILE" ]; then
|
|
write_as_betterdesk "$API_KEY_FILE" "$API_KEY"
|
|
echo "API key from env → $API_KEY_FILE"
|
|
fi
|
|
|
|
# Default enrollment policy.
|
|
# Fresh volumes default to "managed": stock RustDesk clients are queued for
|
|
# operator approval instead of connecting silently. Pre-existing installs keep
|
|
# their current behavior (Go default "open", or whatever was set via the panel
|
|
# and persisted in the database). A volume that already contains a server key
|
|
# or SQLite database is treated as pre-existing.
|
|
if [ -z "${ENROLLMENT_MODE:-}" ]; then
|
|
ENROLLMENT_SENTINEL="/opt/rustdesk/.enrollment_initialized"
|
|
if [ ! -f "$ENROLLMENT_SENTINEL" ]; then
|
|
if [ -f /opt/rustdesk/db_v2.sqlite3 ] || [ -f /opt/rustdesk/id_ed25519 ]; then
|
|
echo "Enrollment: preserving existing policy (pre-existing volume)"
|
|
else
|
|
export ENROLLMENT_MODE="managed"
|
|
echo "Enrollment: managed (fresh install — new devices need approval)"
|
|
fi
|
|
touch_as_betterdesk "$ENROLLMENT_SENTINEL" 2>/dev/null || true
|
|
fi
|
|
fi
|
|
# Always export so supervisord's %(ENV_ENROLLMENT_MODE)s interpolation resolves.
|
|
# An empty value is ignored by the Go server (keeps default/DB-restored mode).
|
|
export ENROLLMENT_MODE="${ENROLLMENT_MODE:-}"
|
|
|
|
# Verify write access — SQLite WAL mode requires writable directory (Issue #78)
|
|
if ! touch_as_betterdesk /opt/rustdesk/.write_test 2>/dev/null; then
|
|
echo ""
|
|
echo "ERROR: /opt/rustdesk is NOT writable by the betterdesk user (UID 10001)."
|
|
echo " SQLite WAL mode requires write access to the database directory."
|
|
echo " If using bind mounts, run: chown -R 10001:10001 /path/to/your/data"
|
|
echo " Or use Docker named volumes instead of bind mounts."
|
|
echo ""
|
|
fi
|
|
rm -f /opt/rustdesk/.write_test 2>/dev/null || true
|
|
# Fix private key permissions (volume mounts may preserve wrong UID/mode)
|
|
if [ -f /opt/rustdesk/id_ed25519 ]; then
|
|
chmod 600 /opt/rustdesk/id_ed25519 2>/dev/null || true
|
|
chown betterdesk:betterdesk /opt/rustdesk/id_ed25519 2>/dev/null || true
|
|
fi
|
|
|
|
# BD-2026-007: Warn about weak default secrets
|
|
if [ -n "${SESSION_SECRET}" ] && [ ${#SESSION_SECRET} -lt 32 ]; then
|
|
echo "WARNING [SECURITY]: SESSION_SECRET is shorter than 32 characters — generate a stronger secret"
|
|
fi
|
|
if [ -n "${ADMIN_PASSWORD}" ] && [ ${#ADMIN_PASSWORD} -lt 12 ]; then
|
|
echo "WARNING [SECURITY]: ADMIN_PASSWORD is shorter than 12 characters — use a stronger password"
|
|
fi
|
|
|
|
# Determine database DSN for Go server
|
|
# DB_URL env var is read by Go server's config.LoadEnv()
|
|
if [ "${DB_TYPE}" = "postgres" ] || [ "${DB_TYPE}" = "postgresql" ]; then
|
|
if [ -n "${DATABASE_URL}" ]; then
|
|
export DB_URL="${DATABASE_URL}"
|
|
echo "Database: PostgreSQL (${DATABASE_URL%%@*}@***)"
|
|
else
|
|
echo "WARNING: DB_TYPE=postgres but DATABASE_URL not set — falling back to SQLite"
|
|
export DB_URL="/opt/rustdesk/db_v2.sqlite3"
|
|
fi
|
|
else
|
|
export DB_URL="${DB_PATH:-/opt/rustdesk/db_v2.sqlite3}"
|
|
export AUTH_DB_PATH="${AUTH_DB_PATH:-/app/data/auth.db}"
|
|
echo "Database: SQLite (${DB_URL})"
|
|
echo "Panel sync: ${AUTH_DB_PATH}"
|
|
fi
|
|
|
|
# Wait for PostgreSQL if configured
|
|
if [ "${DB_TYPE}" = "postgres" ] || [ "${DB_TYPE}" = "postgresql" ]; then
|
|
if [ -n "${DATABASE_URL}" ]; then
|
|
echo "Waiting for PostgreSQL..."
|
|
PG_HOST=$(echo "$DATABASE_URL" | sed -n 's|.*@\([^:/]*\).*|\1|p')
|
|
PG_PORT=$(echo "$DATABASE_URL" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
|
PG_PORT=${PG_PORT:-5432}
|
|
RETRIES=0
|
|
MAX_RETRIES=30
|
|
while [ "$RETRIES" -lt "$MAX_RETRIES" ]; do
|
|
if nc -z "$PG_HOST" "$PG_PORT" 2>/dev/null; then
|
|
echo " PostgreSQL is ready ($PG_HOST:$PG_PORT)"
|
|
break
|
|
fi
|
|
RETRIES=$((RETRIES + 1))
|
|
echo " Waiting... ($RETRIES/$MAX_RETRIES)"
|
|
sleep 2
|
|
done
|
|
if [ "$RETRIES" -ge "$MAX_RETRIES" ]; then
|
|
echo " WARNING: PostgreSQL not reachable — starting anyway"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Ensure Go server uses correct signal port (not NODE.js PORT)
|
|
export SIGNAL_PORT="${SIGNAL_PORT:-21116}"
|
|
export SIGNAL_RATE_LIMIT_PER_IP="${SIGNAL_RATE_LIMIT_PER_IP:-20}"
|
|
|
|
# Ensure Node.js Client API binds to all interfaces (not just localhost)
|
|
export API_HOST="${API_HOST:-0.0.0.0}"
|
|
export HOST="${HOST:-0.0.0.0}"
|
|
|
|
# Relay server address: if not explicitly set, try to auto-detect public IP.
|
|
# Inside Docker, the Go server's own detection may return the container's
|
|
# internal IP (172.x.x.x) which is unreachable by remote clients.
|
|
if [ -z "${RELAY_SERVERS:-}" ]; then
|
|
DETECTED_IP=""
|
|
if command -v curl >/dev/null 2>&1; then
|
|
DETECTED_IP=$(curl -4 -sf --max-time 5 https://checkip.amazonaws.com 2>/dev/null \
|
|
|| curl -4 -sf --max-time 5 https://api.ipify.org 2>/dev/null \
|
|
|| curl -4 -sf --max-time 5 https://ifconfig.me/ip 2>/dev/null || true)
|
|
DETECTED_IP=$(echo "$DETECTED_IP" | tr -d '[:space:]')
|
|
fi
|
|
if [ -n "$DETECTED_IP" ]; then
|
|
# Verify it's not a private/Docker IP
|
|
case "$DETECTED_IP" in
|
|
10.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*|192.168.*|127.*)
|
|
echo "WARNING: Auto-detected IP ($DETECTED_IP) is private — relay may fail for remote clients."
|
|
echo " Set RELAY_SERVERS=YOUR.PUBLIC.IP in docker-compose.single.yml"
|
|
;;
|
|
*)
|
|
export RELAY_SERVERS="$DETECTED_IP"
|
|
echo "Relay IP: $DETECTED_IP (auto-detected)"
|
|
;;
|
|
esac
|
|
else
|
|
echo "WARNING: Could not auto-detect public IP for relay."
|
|
echo " Set RELAY_SERVERS=YOUR.PUBLIC.IP in docker-compose.single.yml"
|
|
fi
|
|
else
|
|
echo "Relay IP: $RELAY_SERVERS (from env)"
|
|
fi
|
|
export RELAY_SERVERS="${RELAY_SERVERS:-}"
|
|
|
|
echo ""
|
|
echo "Starting services via supervisord..."
|
|
if [ "${HTTPS_ENABLED:-false}" = "true" ]; then
|
|
echo " Web Console: https://localhost:${HTTPS_PORT:-5443}"
|
|
echo " HTTP redirect: http://localhost:${PORT:-5000} → https://…:${HTTPS_PORT:-5443}"
|
|
else
|
|
echo " Web Console: http://localhost:${PORT:-5000}"
|
|
fi
|
|
echo " Go API: http://localhost:21121/api"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
# Start supervisord (manages both processes)
|
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/betterdesk.conf
|