mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 01:27:11 +00:00
e89a6889eb
Map ADMIN_PASSWORD into compose containers, log bootstrap set=yes/no, and document amd64-only AIO :dev plus split/dev-arm workarounds for arm64. Refs #385 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
231 lines
7.9 KiB
Bash
231 lines
7.9 KiB
Bash
#!/bin/sh
|
|
# Pre-bootstrap shared admin credentials for Docker (issue #385).
|
|
# Go server and Node.js console must use the same password on first start.
|
|
# Writes /opt/rustdesk/.admin_credentials and exports INIT_ADMIN_* / DEFAULT_ADMIN_*.
|
|
# Split images source this script concurrently, so creation is serialized on
|
|
# the shared volume.
|
|
set -e
|
|
|
|
CREDS_DIR="${RUSTDESK_PATH:-/opt/rustdesk}"
|
|
CREDS_FILE="${CREDS_DIR}/.admin_credentials"
|
|
LOCK_DIR="${CREDS_FILE}.lock"
|
|
ADMIN_USER="${INIT_ADMIN_USER:-${DEFAULT_ADMIN_USERNAME:-${ADMIN_USERNAME:-admin}}}"
|
|
|
|
# Map public ADMIN_* aliases to internal seed vars (same as entrypoints).
|
|
if [ -n "${ADMIN_USERNAME:-}" ] && [ -z "${INIT_ADMIN_USER:-}" ]; then
|
|
export INIT_ADMIN_USER="$ADMIN_USERNAME"
|
|
fi
|
|
if [ -n "${ADMIN_USERNAME:-}" ] && [ -z "${DEFAULT_ADMIN_USERNAME:-}" ]; then
|
|
export DEFAULT_ADMIN_USERNAME="$ADMIN_USERNAME"
|
|
fi
|
|
if [ -n "${ADMIN_PASSWORD:-}" ] && [ -z "${INIT_ADMIN_PASS:-}" ]; then
|
|
export INIT_ADMIN_PASS="$ADMIN_PASSWORD"
|
|
fi
|
|
if [ -n "${ADMIN_PASSWORD:-}" ] && [ -z "${DEFAULT_ADMIN_PASSWORD:-}" ]; then
|
|
export DEFAULT_ADMIN_PASSWORD="$ADMIN_PASSWORD"
|
|
fi
|
|
|
|
ADMIN_USER="${INIT_ADMIN_USER:-${DEFAULT_ADMIN_USERNAME:-admin}}"
|
|
|
|
sync_exports() {
|
|
export INIT_ADMIN_USER="${INIT_ADMIN_USER:-$ADMIN_USER}"
|
|
export DEFAULT_ADMIN_USERNAME="${DEFAULT_ADMIN_USERNAME:-$ADMIN_USER}"
|
|
if [ -n "${INIT_ADMIN_PASS:-}" ]; then
|
|
# INIT_ADMIN_PASS and DEFAULT_ADMIN_PASSWORD feed different
|
|
# processes in the split image. Never allow two configured values.
|
|
export DEFAULT_ADMIN_PASSWORD="$INIT_ADMIN_PASS"
|
|
elif [ -n "${DEFAULT_ADMIN_PASSWORD:-}" ]; then
|
|
export INIT_ADMIN_PASS="${INIT_ADMIN_PASS:-$DEFAULT_ADMIN_PASSWORD}"
|
|
fi
|
|
}
|
|
|
|
run_as_betterdesk() {
|
|
if [ "$(id -u)" = "0" ] && command -v su-exec >/dev/null 2>&1; then
|
|
su-exec betterdesk "$@"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
parse_creds_password() {
|
|
_file="$1"
|
|
if [ ! -f "$_file" ]; then
|
|
return 1
|
|
fi
|
|
# The file is intentionally mode 0600 and owned by betterdesk. Root in
|
|
# hardened containers has no CAP_DAC_OVERRIDE, so read it as the app user.
|
|
_line=$(run_as_betterdesk grep -m1 '^Admin Password:' "$_file" 2>/dev/null || true)
|
|
_pass=$(printf '%s\n' "$_line" | sed 's/^Admin Password:[[:space:]]*//')
|
|
if [ -n "$_pass" ]; then
|
|
printf '%s\n' "$_pass"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
write_as_betterdesk() {
|
|
_path="$1"
|
|
_content="$2"
|
|
if [ "$(id -u)" = "0" ] && command -v su-exec >/dev/null 2>&1; then
|
|
printf '%s' "$_content" | su-exec betterdesk sh -c "umask 077; cat > \"$_path\""
|
|
else
|
|
umask 077
|
|
printf '%s' "$_content" > "$_path"
|
|
fi
|
|
}
|
|
|
|
move_as_betterdesk() {
|
|
run_as_betterdesk mv "$1" "$2"
|
|
}
|
|
|
|
export_bootstrap_password() {
|
|
_pass="$1"
|
|
export INIT_ADMIN_PASS="$_pass"
|
|
export DEFAULT_ADMIN_PASSWORD="$_pass"
|
|
sync_exports
|
|
}
|
|
|
|
read_existing_credentials() {
|
|
_existing_pass=$(parse_creds_password "$CREDS_FILE" || true)
|
|
if [ -n "$_existing_pass" ]; then
|
|
export_bootstrap_password "$_existing_pass"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
primary_database_has_users() {
|
|
_primary_db="${DB_PATH:-${DB_URL:-${CREDS_DIR}/db_v2.sqlite3}}"
|
|
case "$_primary_db" in
|
|
postgres://*|postgresql://*) return 1 ;;
|
|
esac
|
|
if [ ! -f "$_primary_db" ] || ! command -v sqlite3 >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
_user_count=$(run_as_betterdesk sqlite3 "$_primary_db" \
|
|
"SELECT COUNT(*) FROM users;" 2>/dev/null || true)
|
|
case "$_user_count" in
|
|
''|*[!0-9]*) return 1 ;;
|
|
esac
|
|
[ "$_user_count" -gt 0 ]
|
|
}
|
|
|
|
cleanup_bootstrap_lock() {
|
|
run_as_betterdesk rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
}
|
|
|
|
wait_for_bootstrap_credentials() {
|
|
_waited=0
|
|
_max_wait="${BOOTSTRAP_CREDENTIALS_WAIT_SECONDS:-120}"
|
|
while [ "$_waited" -lt "$_max_wait" ]; do
|
|
if read_existing_credentials; then
|
|
return 0
|
|
fi
|
|
if [ ! -d "$LOCK_DIR" ]; then
|
|
return 1
|
|
fi
|
|
sleep 1
|
|
_waited=$((_waited + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Already configured via env — keep Go and Node in sync, and still publish
|
|
# .admin_credentials on a fresh volume so operators / show-admin-credentials
|
|
# can read the same password (#385).
|
|
if [ -n "${INIT_ADMIN_PASS:-}" ] || [ -n "${DEFAULT_ADMIN_PASSWORD:-}" ]; then
|
|
sync_exports
|
|
_env_pass="${INIT_ADMIN_PASS:-$DEFAULT_ADMIN_PASSWORD}"
|
|
if [ -n "$_env_pass" ]; then
|
|
echo "Bootstrap: INIT_ADMIN_PASS/DEFAULT_ADMIN_PASSWORD set=yes (password not logged)"
|
|
run_as_betterdesk mkdir -p "$CREDS_DIR" 2>/dev/null || true
|
|
if [ ! -f "$CREDS_FILE" ] && ! primary_database_has_users; then
|
|
_creds_content="Admin Username: ${ADMIN_USER}
|
|
Admin Password: ${_env_pass}
|
|
Generated by: BetterDesk Docker bootstrap (ADMIN_PASSWORD)
|
|
Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
"
|
|
_tmp_file="${CREDS_FILE}.tmp.$$"
|
|
write_as_betterdesk "$_tmp_file" "$_creds_content"
|
|
move_as_betterdesk "$_tmp_file" "$CREDS_FILE"
|
|
echo "Bootstrap admin credentials → ${CREDS_FILE} (from ADMIN_PASSWORD / INIT_ADMIN_PASS)"
|
|
fi
|
|
fi
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
echo "Bootstrap: INIT_ADMIN_PASS/DEFAULT_ADMIN_PASSWORD set=no (will use credentials file or generate)"
|
|
|
|
run_as_betterdesk mkdir -p "$CREDS_DIR" 2>/dev/null || true
|
|
|
|
# Reuse an existing shared credential before trying to acquire the creation
|
|
# lock. The second check after mkdir closes the check-then-create race.
|
|
if read_existing_credentials; then
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
# A missing credentials file on an existing SQLite installation must not
|
|
# create a misleading replacement password. Existing users keep their hash;
|
|
# recovery must use the normal password-reset flow.
|
|
if primary_database_has_users; then
|
|
echo "WARN: ${CREDS_FILE} is missing, but the primary database already has users." >&2
|
|
echo " No replacement bootstrap password was generated; use password reset." >&2
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
if run_as_betterdesk mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
trap cleanup_bootstrap_lock EXIT HUP INT TERM
|
|
|
|
if read_existing_credentials; then
|
|
cleanup_bootstrap_lock
|
|
trap - EXIT HUP INT TERM
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
if primary_database_has_users; then
|
|
cleanup_bootstrap_lock
|
|
trap - EXIT HUP INT TERM
|
|
echo "WARN: ${CREDS_FILE} is missing, but the primary database already has users." >&2
|
|
echo " No replacement bootstrap password was generated; use password reset." >&2
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
if [ -e "$CREDS_FILE" ]; then
|
|
cleanup_bootstrap_lock
|
|
trap - EXIT HUP INT TERM
|
|
echo "ERROR: ${CREDS_FILE} exists but does not contain a readable admin password." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Fresh install: the lock owner generates exactly one password and
|
|
# publishes it with an atomic rename. Other containers wait above.
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
_new_pass=$(openssl rand -hex 16)
|
|
else
|
|
_new_pass=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
|
fi
|
|
|
|
_creds_content="Admin Username: ${ADMIN_USER}
|
|
Admin Password: ${_new_pass}
|
|
Generated by: BetterDesk Docker bootstrap
|
|
Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
"
|
|
_tmp_file="${CREDS_FILE}.tmp.$$"
|
|
write_as_betterdesk "$_tmp_file" "$_creds_content"
|
|
move_as_betterdesk "$_tmp_file" "$CREDS_FILE"
|
|
|
|
export_bootstrap_password "$_new_pass"
|
|
cleanup_bootstrap_lock
|
|
trap - EXIT HUP INT TERM
|
|
echo "Bootstrap admin credentials → ${CREDS_FILE}"
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
# Another split container owns the lock. Generating a fallback here would
|
|
# recreate issue #385, so wait for its atomic publication.
|
|
if wait_for_bootstrap_credentials; then
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
|
|
echo "ERROR: timed out waiting for shared admin credentials at ${CREDS_FILE}" >&2
|
|
echo " Remove the stale ${LOCK_DIR} only after confirming no BetterDesk container is bootstrapping." >&2
|
|
exit 1
|