mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-10-27 08:14:17 +00:00
wger: clearer Postgres connection errors in the PowerSync import, reuse SECRET_KEY, doc fixes (#3123)
- target_database_state() reports the real connection error, fails at once on errors waiting cannot fix (wrong password, missing role/database, pg_hba) and fails closed on any unexpected probe output. - 89-powersync-run.sh reuses SECRET_KEY from /data/secrets.env, so its manage.py calls no longer print upstream's missing-key warning. - README: internal Postgres hostname in the example; safe password characters (PowerSync does not percent-decode the URI). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09ca012050
commit
876729f0fb
4 files changed
+76
-17
No files matched your search
@@ -1,3 +1,9 @@
|
||||
## 2.8.0-dev.1.4 (2026-10-21)
|
||||
|
||||
- Show the PostgreSQL connection error once while the PowerSync import check waits, retry transient connection failures, and fail immediately for errors such as a wrong password or missing role/database (#3123).
|
||||
- Reuse the persistent `SECRET_KEY` before PowerSync runs wger management commands, so the session-persistence warning no longer appears after the add-on has started once (#3123).
|
||||
- README: use the Postgres 17 add-on's internal hostname in the PowerSync example, so port 5432 can stay closed, and say which characters a database password can safely contain (#3123).
|
||||
|
||||
## 2.8.0-dev.1.3 (2026-10-20)
|
||||
|
||||
- Fix the PowerSync SQLite import failing with `CommandError: Unknown model: admin.logentry`, which kept wger from starting. wger does not install Django's admin app, so the import no longer asks `dumpdata` to exclude its log table (#3134).
|
||||
|
||||
+11
-2
@@ -168,9 +168,9 @@ option (the `CONFIG_LOCATION` config.yaml file documented above works for these
|
||||
```yaml
|
||||
env_vars:
|
||||
- name: PS_DATABASE_URI
|
||||
value: postgres://wger:yourpassword@192.168.1.10:5432/wger
|
||||
value: postgres://wger:yourpassword@db21ed7f-postgres-latest:5432/wger
|
||||
- name: PS_STORAGE_PG_URI
|
||||
value: postgres://powersync_storage:anotherpassword@192.168.1.10:5432/wger
|
||||
value: postgres://powersync_storage:anotherpassword@db21ed7f-postgres-latest:5432/wger
|
||||
- name: SITE_URL
|
||||
value: https://wger.yourdomain.com
|
||||
```
|
||||
@@ -184,6 +184,15 @@ env_vars:
|
||||
add-on creates. It must be set, it has no useful default.
|
||||
- `SITE_URL` must be an absolute `http://` or `https://` URL with no path.
|
||||
|
||||
`db21ed7f-postgres-latest` is the internal hostname of this repository's Postgres 17
|
||||
add-on, so it works without exposing port 5432. A LAN IP also works when PostgreSQL is
|
||||
running on an external server.
|
||||
|
||||
Use passwords made only of letters, digits and `-._~`. Characters such as `@`, `:`,
|
||||
`/`, `?`, `#` or `%` break the URI, and percent-encoding them is no way out: wger
|
||||
decodes `%40` back to `@`, but PowerSync passes it to PostgreSQL unchanged, so its
|
||||
connections fail to authenticate.
|
||||
|
||||
Set these through the `env_vars` option rather than the `CONFIG_LOCATION` file if the
|
||||
password contains a `$` or a backtick. Both routes work, but the config.yaml route
|
||||
writes the value unquoted into the add-on's environment file, where the shell expands
|
||||
|
||||
+1
-1
@@ -27,5 +27,5 @@ schema:
|
||||
slug: wger
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2.8.0-dev.1.3"
|
||||
version: "2.8.0-dev.1.4"
|
||||
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"
|
||||
@@ -24,6 +24,7 @@ NGINX_PS_DIR=/etc/nginx/ps
|
||||
JWT_DIR=/data/powersync
|
||||
JWT_FILE="$JWT_DIR/jwt.env"
|
||||
ENV_FILE=/.env
|
||||
SECRETS_FILE=/data/secrets.env
|
||||
|
||||
# A fresh install runs bootstrap, collectstatic, migrations and the exercise sync
|
||||
# before gunicorn answers, which upstream and the add-on README both put in the
|
||||
@@ -118,6 +119,19 @@ django_manage() {
|
||||
(cd /home/wger/src && sudo -u wger -E python3 manage.py "$@")
|
||||
}
|
||||
|
||||
# On the first boot 90-run.sh has not generated SECRET_KEY yet, so one warning is expected.
|
||||
prepare_secret_key() {
|
||||
local secret_key=""
|
||||
|
||||
[ -n "${SECRET_KEY:-}" ] && return 0
|
||||
if [ -f "$SECRETS_FILE" ]; then
|
||||
secret_key="$(sed -n 's/^SECRET_KEY=//p' "$SECRETS_FILE" | head -n 1)"
|
||||
fi
|
||||
if [ -n "$secret_key" ]; then
|
||||
export SECRET_KEY="$secret_key"
|
||||
fi
|
||||
}
|
||||
|
||||
# The same, pointed at a SQLite file instead. PS_DATABASE_URI has to go: wger's
|
||||
# settings/main.py checks it before the DJANGO_DB_* block, so it would win.
|
||||
sqlite_manage() {
|
||||
@@ -244,16 +258,27 @@ prepare() {
|
||||
# Mirrors wger's own database_initialised() in wger/tasks.py: a database that cannot be
|
||||
# reached is an error rather than an empty database, and "no users at all" is upstream's
|
||||
# definition of a database that was never set up, because the admin user is created as
|
||||
# the last step of its bootstrap. Prints unreachable, fresh (reachable, no tables yet),
|
||||
# empty (tables but no users) or populated.
|
||||
# the last step of its bootstrap. Only connection failures that waiting cannot fix are
|
||||
# fatal; every other OperationalError is retried. Prints a state, a tab and a one-line
|
||||
# detail.
|
||||
target_database_state() {
|
||||
django_manage shell 2> /dev/null << 'PROBE' | tail -n 1
|
||||
from django.db import DatabaseError, connection
|
||||
from django.db import DatabaseError, OperationalError, connection
|
||||
|
||||
try:
|
||||
connection.ensure_connection()
|
||||
except Exception:
|
||||
print('unreachable')
|
||||
except Exception as error:
|
||||
# psycopg can spread one failure over several lines, one per address tried.
|
||||
message = ' '.join(str(error).split()) or type(error).__name__
|
||||
fatal_messages = (
|
||||
'password authentication failed',
|
||||
'does not exist',
|
||||
'no pg_hba.conf entry',
|
||||
)
|
||||
fatal = not isinstance(error, OperationalError) or any(
|
||||
text in message.lower() for text in fatal_messages
|
||||
)
|
||||
print(f"{'fatal' if fatal else 'retryable'}\t{message}")
|
||||
else:
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
@@ -277,7 +302,7 @@ PROBE
|
||||
# pointed at is the live one.
|
||||
import_sqlite_data() {
|
||||
local sqlite_db="${DJANGO_DB_DATABASE:-/data/database.sqlite}"
|
||||
local waited=0 state=""
|
||||
local waited=0 probe="" state="" detail=""
|
||||
local dump="$IMPORT_DIR/dump.json" copy="$IMPORT_DIR/database.sqlite"
|
||||
|
||||
if [ ! -s "$sqlite_db" ]; then
|
||||
@@ -288,16 +313,33 @@ import_sqlite_data() {
|
||||
|
||||
bashio::log.info "Checking whether the Postgres database already holds wger data"
|
||||
while true; do
|
||||
state="$(target_database_state)"
|
||||
[ "$state" != "unreachable" ] && break
|
||||
if [ "$waited" -ge "$POSTGRES_WAIT_SECONDS" ]; then
|
||||
bashio::log.error "PowerSync: the Postgres database is still unreachable after ${POSTGRES_WAIT_SECONDS}s"
|
||||
probe="$(target_database_state)"
|
||||
IFS=$'\t' read -r state detail <<< "$probe"
|
||||
case "$state" in
|
||||
fresh | empty | populated)
|
||||
break
|
||||
;;
|
||||
retryable)
|
||||
if [ "$waited" -ge "$POSTGRES_WAIT_SECONDS" ]; then
|
||||
bashio::log.error "PowerSync: the Postgres database is still unreachable after ${POSTGRES_WAIT_SECONDS}s: ${detail:-no error detail}"
|
||||
bashio::log.error "PowerSync: the SQLite database itself is untouched at $sqlite_db"
|
||||
return 1
|
||||
fi
|
||||
[ "$waited" -eq 0 ] && bashio::log.info "PowerSync: waiting for the Postgres database: ${detail:-no error detail}"
|
||||
sleep 5
|
||||
waited=$((waited + 5))
|
||||
;;
|
||||
fatal)
|
||||
bashio::log.error "PowerSync: the Postgres connection error cannot be fixed by waiting: ${detail:-no error detail}"
|
||||
bashio::log.error "PowerSync: the SQLite database itself is untouched at $sqlite_db"
|
||||
return 1
|
||||
fi
|
||||
[ "$waited" -eq 0 ] && bashio::log.info "PowerSync: waiting for the Postgres database"
|
||||
sleep 5
|
||||
waited=$((waited + 5))
|
||||
;;
|
||||
*)
|
||||
bashio::log.error "PowerSync: the Postgres probe returned an unexpected result: ${probe:-no output}"
|
||||
bashio::log.error "PowerSync: the SQLite database itself is untouched at $sqlite_db"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$state" = "populated" ]; then
|
||||
@@ -371,6 +413,8 @@ import_sqlite_data() {
|
||||
return 0
|
||||
}
|
||||
|
||||
prepare_secret_key
|
||||
|
||||
if ! prepare; then
|
||||
# The marker armed above is deliberately left in place; see the comment there.
|
||||
exit 0
|
||||
|
||||
Reference in new issue
Block a user