diff --git a/immich/CHANGELOG.md b/immich/CHANGELOG.md index b9985445e7..88a987a0b4 100644 --- a/immich/CHANGELOG.md +++ b/immich/CHANGELOG.md @@ -1,4 +1,7 @@ +## 3.1.0.1 (2026-08-17) +- Fix `password authentication failed for user` when `DB_PASSWORD` contains special characters. Passwords are now URI-encoded before being used in the psql connection string, and SQL-escaped before being used in `CREATE`/`ALTER USER` statements + ## 3.1.0 (2026-08-01) - Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases) diff --git a/immich/config.yaml b/immich/config.yaml index e5b4faefd6..00e8945323 100644 --- a/immich/config.yaml +++ b/immich/config.yaml @@ -141,6 +141,6 @@ slug: immich udev: true url: https://github.com/alexbelgium/hassio-addons usb: true -version: "3.1.0" +version: "3.1.0.1" video: true webui: http://[HOST]:[PORT:8080] diff --git a/immich/rootfs/etc/cont-init.d/99-run.sh b/immich/rootfs/etc/cont-init.d/99-run.sh index 50e8b0c6cf..8ad46f6a5d 100755 --- a/immich/rootfs/etc/cont-init.d/99-run.sh +++ b/immich/rootfs/etc/cont-init.d/99-run.sh @@ -98,10 +98,11 @@ setup_root_user() { fi # Check if the root user exists. - if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then + if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then bashio::log.info "Root user does not exist. Creating root user with DB_ROOT_PASSWORD..." - psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF -CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${DB_ROOT_PASSWORD}'; + local root_password_sql="${DB_ROOT_PASSWORD//\'/\'\'}" + psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF +CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${root_password_sql}'; EOF else bashio::log.info "Root user exists with a non-default password. No migration needed." @@ -113,10 +114,10 @@ setup_database() { bashio::log.info "Setting up external PostgreSQL database..." # Create the database if it does not exist - if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \ + if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \ "SELECT 1 FROM pg_database WHERE datname='${DB_DATABASE_NAME}';" | grep -q 1; then bashio::log.info "Database does not exist. Creating it now..." - psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF + psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF CREATE DATABASE ${DB_DATABASE_NAME}; EOF else @@ -124,20 +125,21 @@ EOF fi # Ensure the user exists and update its password - psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF + local db_password_sql="${DB_PASSWORD//\'/\'\'}" + psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USERNAME}') THEN - CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}'; + CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}'; ELSE - ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}'; + ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}'; END IF; END \$\$; EOF # Ensure the user has full privileges on the database - psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF + psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME}; EOF @@ -147,7 +149,7 @@ EOF # Function to check if the vectors (pgvecto.rs) extension is available on the server check_vector_extension() { echo "Checking if 'vectors' extension is available for database '${DB_DATABASE_NAME}'..." - RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';") + RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';") if [[ "$RESULT" == "1" ]]; then echo "✅ 'vectors' extension is available." return 0 @@ -163,7 +165,7 @@ check_vector_extension() { # itself on first startup; checking pg_extension would false-warn on every fresh install. check_vchord_extension() { echo "Checking if 'vchord' extension is available for database '${DB_DATABASE_NAME}'..." - RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';") + RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';") if [[ "$RESULT" == "1" ]]; then echo "✅ 'vchord' extension is available." return 0 @@ -187,6 +189,14 @@ export DB_PORT="$(bashio::config 'DB_PORT')" export JWT_SECRET="$(bashio::config 'JWT_SECRET')" export DB_HOSTNAME="$(bashio::config 'DB_HOSTNAME')" +# libpq percent-decodes the userinfo part of a postgres:// URI, so credentials +# containing reserved characters (% @ / : ? #) are misread and every psql call +# below fails with "password authentication failed". Encode them once here and +# use the encoded copies in URIs only - the app itself still gets the raw value +# through export_db_env. Same approach as the postgres_15/postgres_17 addons. +export DB_USERNAME_URI="$(jq -rn --arg x "$DB_USERNAME" '$x|@uri')" +export DB_PASSWORD_URI="$(jq -rn --arg x "$DB_PASSWORD" '$x|@uri')" + if bashio::config.true 'VIPS_NOVECTOR'; then export VIPS_NOVECTOR="1" fi