Kaynağa Gözat

Random PostgreSQL role + post-install credentials display (#4608)

* feat(install): random PostgreSQL role + post-install credentials display

The local-Postgres installer used to bake in a static role name (`xui`)
and only printed `PostgreSQL ([email protected]:5432/xui)` at the end of
install, leaving operators without the random password or any hint of
how to connect from the shell.

Two changes:

- install_postgres_local now generates an 8-char random role name
  alongside the random password, and double-quotes identifiers in the
  CREATE/ALTER statements (a random alphanumeric may start with a digit,
  which Postgres rejects for unquoted identifiers).

- After a successful local install, a dedicated "PostgreSQL Credentials"
  block is rendered in the summary — DB / user / pass / host / port /
  DSN / env-file path, plus ready-to-paste psql commands for both the
  postgres superuser and the new role. Credentials cross the subshell
  boundary via a 0600 tmpfile (PG_CRED_FILE) that the parent shell
  sources and unlinks; the PG_* vars are unset after display.

Only fires for the local-install flow; the external-DSN path is
unchanged.

* fix(install): address Copilot review on Postgres install flow

- Use mktemp (unguessable, 0600) instead of /tmp/x-ui-pg-creds.$$ and
  cleanup in both success and failure paths to close the symlink/race
  attack on the predictable filename.
- In install_postgres_local, capture the prior umask and restore it
  after writing PG_CRED_FILE; return 1 if the write fails so the
  caller does not source nothing and label the install with empty
  PG_* vars.
- On reinstall, reuse the existing xui DB owner instead of generating
  a fresh role each run, so existing tables stay accessible after a
  re-run; only the password is rotated. Falls back to a fresh random
  role when the DB does not exist or is owned by postgres.
Sanaei 8 saat önce
ebeveyn
işleme
058c030e81
1 değiştirilmiş dosya ile 88 ekleme ve 13 silme
  1. 88 13
      install.sh

+ 88 - 13
install.sh

@@ -111,10 +111,11 @@ gen_random_string() {
 }
 
 install_postgres_local() {
-    local pg_user="xui"
-    local pg_db="xui"
-    local pg_pass
+    local pg_user pg_pass
     pg_pass=$(gen_random_string 24)
+    local pg_db="xui"
+    local pg_host="127.0.0.1"
+    local pg_port="5432"
 
     case "${release}" in
         ubuntu | debian | armbian)
@@ -170,20 +171,50 @@ install_postgres_local() {
         sleep 1
     done
 
-    # Idempotent role/db creation.
+    local existing_owner=""
+    existing_owner=$(sudo -u postgres psql -tAc \
+        "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_database WHERE datname='${pg_db}'" 2> /dev/null \
+        | tr -d '[:space:]')
+    if [[ -n "${existing_owner}" && "${existing_owner}" != "postgres" ]]; then
+        pg_user="${existing_owner}"
+    else
+        pg_user=$(gen_random_string 8)
+    fi
+
+    # Idempotent role/db creation. Identifiers are double-quoted because a
+    # random username may start with a digit, which Postgres rejects unquoted.
     sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${pg_user}'" 2> /dev/null \
         | grep -q 1 \
-        || sudo -u postgres psql -c "CREATE USER ${pg_user} WITH PASSWORD '${pg_pass}';" >&2 || return 1
+        || sudo -u postgres psql -c "CREATE USER \"${pg_user}\" WITH PASSWORD '${pg_pass}';" >&2 || return 1
 
     sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${pg_db}'" 2> /dev/null \
         | grep -q 1 \
-        || sudo -u postgres psql -c "CREATE DATABASE ${pg_db} OWNER ${pg_user};" >&2 || return 1
+        || sudo -u postgres psql -c "CREATE DATABASE \"${pg_db}\" OWNER \"${pg_user}\";" >&2 || return 1
 
-    sudo -u postgres psql -c "ALTER USER ${pg_user} WITH PASSWORD '${pg_pass}';" >&2 || return 1
+    sudo -u postgres psql -c "ALTER USER \"${pg_user}\" WITH PASSWORD '${pg_pass}';" >&2 || return 1
 
     local pg_pass_enc
     pg_pass_enc=$(printf '%s' "${pg_pass}" | sed -e 's/%/%25/g' -e 's/:/%3A/g' -e 's/@/%40/g' -e 's|/|%2F|g' -e 's/?/%3F/g' -e 's/#/%23/g')
-    echo "postgres://${pg_user}:${pg_pass_enc}@127.0.0.1:5432/${pg_db}?sslmode=disable"
+
+    if [[ -n "${PG_CRED_FILE:-}" ]]; then
+        local prev_umask
+        prev_umask=$(umask)
+        umask 077
+        if ! cat > "${PG_CRED_FILE}" << EOF; then
+PG_USER=${pg_user}
+PG_PASS=${pg_pass}
+PG_HOST=${pg_host}
+PG_PORT=${pg_port}
+PG_DB=${pg_db}
+EOF
+            umask "${prev_umask}"
+            echo -e "${red}Failed to write PostgreSQL credentials to ${PG_CRED_FILE}${plain}" >&2
+            return 1
+        fi
+        umask "${prev_umask}"
+    fi
+
+    echo "postgres://${pg_user}:${pg_pass_enc}@${pg_host}:${pg_port}/${pg_db}?sslmode=disable"
     return 0
 }
 
@@ -823,7 +854,7 @@ config_after_install() {
             echo -e "${green}═══════════════════════════════════════════${plain}"
             echo -e "${green}     Database Selection                    ${plain}"
             echo -e "${green}═══════════════════════════════════════════${plain}"
-            echo -e "  1) SQLite     (default — recommended for < 1000 clients)"
+            echo -e "  1) SQLite     (default — recommended for < 500 clients)"
             echo -e "  2) PostgreSQL (recommended for high client counts / many nodes)"
             read -rp "Choose [1]: " db_choice
             db_choice="${db_choice:-1}"
@@ -843,6 +874,7 @@ config_after_install() {
 
                 local xui_dsn=""
                 local pg_mode=""
+                local pg_local_installed=0
                 while [[ -z "$xui_dsn" ]]; do
                     echo ""
                     echo -e "  1) Install PostgreSQL locally and create a dedicated user/db (recommended)"
@@ -857,9 +889,23 @@ config_after_install() {
                         db_label="PostgreSQL (external)"
                     else
                         echo -e "${yellow}Installing PostgreSQL — this may take a moment...${plain}"
-                        if xui_dsn=$(install_postgres_local); then
-                            db_label="PostgreSQL ([email protected]:5432/xui)"
+                        local pg_cred_file
+                        pg_cred_file=$(mktemp 2> /dev/null) || pg_cred_file=$(mktemp -t x-ui-pg-creds.XXXXXXXX)
+                        if [[ -z "${pg_cred_file}" ]]; then
+                            echo -e "${red}Failed to create temporary credentials file.${plain}"
+                            xui_dsn=""
+                            continue
+                        fi
+                        if xui_dsn=$(PG_CRED_FILE="${pg_cred_file}" install_postgres_local); then
+                            pg_local_installed=1
+                            if [[ -r "${pg_cred_file}" ]]; then
+                                # shellcheck disable=SC1090
+                                source "${pg_cred_file}"
+                            fi
+                            rm -f "${pg_cred_file}"
+                            db_label="PostgreSQL (${PG_USER}@${PG_HOST}:${PG_PORT}/${PG_DB})"
                         else
+                            rm -f "${pg_cred_file}"
                             echo ""
                             echo -e "${red}PostgreSQL installation failed.${plain}"
                             echo -e "  1) Retry local install"
@@ -870,8 +916,15 @@ config_after_install() {
                             pg_fail="${pg_fail:-1}"
                             case "$pg_fail" in
                                 2) pg_mode="2" ;;
-                                3) echo -e "${red}Install aborted.${plain}"; exit 1 ;;
-                                4) db_choice="1"; xui_dsn=""; break ;;
+                                3)
+                                    echo -e "${red}Install aborted.${plain}"
+                                    exit 1
+                                    ;;
+                                4)
+                                    db_choice="1"
+                                    xui_dsn=""
+                                    break
+                                    ;;
                                 *) xui_dsn="" ;;
                             esac
                         fi
@@ -935,6 +988,28 @@ EOF
             else
                 echo -e "${yellow}⚠ SSL Certificate: Skipped — panel is HTTP-only. Use a reverse proxy or SSH tunnel.${plain}"
             fi
+
+            if [[ "$db_choice" == "2" && "$pg_local_installed" == "1" ]]; then
+                echo ""
+                echo -e "${green}═══════════════════════════════════════════${plain}"
+                echo -e "${green}     PostgreSQL Credentials               ${plain}"
+                echo -e "${green}═══════════════════════════════════════════${plain}"
+                echo -e "${green}DB Name:    ${PG_DB}${plain}"
+                echo -e "${green}Username:   ${PG_USER}${plain}"
+                echo -e "${green}Password:   ${PG_PASS}${plain}"
+                echo -e "${green}Host:       ${PG_HOST}${plain}"
+                echo -e "${green}Port:       ${PG_PORT}${plain}"
+                echo -e "${green}DSN:        ${xui_dsn}${plain}"
+                echo -e "${green}Env file:   ${xui_env_file}${plain}"
+                echo -e "${green}-------------------------------------------${plain}"
+                echo -e "${green}Connect from this server:${plain}"
+                echo -e "  ${blue}sudo -u postgres psql -d ${PG_DB}${plain}      (as the postgres superuser)"
+                echo -e "  ${blue}PGPASSWORD='${PG_PASS}' psql -h ${PG_HOST} -p ${PG_PORT} -U ${PG_USER} -d ${PG_DB}${plain}"
+                echo -e "${green}═══════════════════════════════════════════${plain}"
+                echo -e "${yellow}⚠ The panel reads these credentials from ${xui_env_file}.${plain}"
+                echo -e "${yellow}⚠ Save the password — it is not stored anywhere else in plain text.${plain}"
+                unset PG_USER PG_PASS PG_HOST PG_PORT PG_DB
+            fi
         else
             local config_webBasePath=$(gen_random_string 18)
             echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"