diff --git a/README.md b/README.md index d9b7e1e..6089f88 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,46 @@ cd Bambu-Run bash setup.sh ``` +### Pointing at an External Postgres Database (Read-Only) + +If you already have printer data in a Postgres database (e.g. shared with +another Django project using the same `infrastructure_*` tables) and want this +install to act as a read-only viewer instead of collecting its own data: + +```bash +cd ~/Bambu-Run + +# 1. Stop and disable the collector — it's the only thing that writes to the +# database (PrinterMetrics, FilamentSnapshot, PrintJob). A read-only DB +# user will make it crash-loop, so turn it off. +systemctl --user stop bambu-run-collector.service +systemctl --user disable bambu-run-collector.service + +# 2. Install the Postgres driver +.venv/bin/pip install ".[postgres]" + +# 3. Edit .env — point at Postgres with a read-only user, and switch sessions +# to signed cookies so login doesn't need to write to django_session +cat >> .env <<'EOF' +DB_ENGINE=postgresql +DB_NAME=your_db_name +DB_USER=your_readonly_user +DB_PASSWORD=your_readonly_password +DB_HOST=your_db_host +DB_PORT=5432 +SESSION_ENGINE=signed_cookies +EOF + +# 4. Restart the web service (do NOT run `manage.py migrate` — the schema +# already exists in that database, and a read-only user can't apply +# migrations anyway) +systemctl --user restart bambu-run-web.service +``` + +You'll need a login that already exists in that database's `auth_user` +table — this install can't create one itself without write access. If MCP is +enabled, it becomes read-only too, since its tools only ever `SELECT`. + --- ## Docker Setup diff --git a/standalone/settings.py b/standalone/settings.py index b52bf64..48e8d63 100644 --- a/standalone/settings.py +++ b/standalone/settings.py @@ -87,6 +87,15 @@ else: } } +# Session storage — DB-backed by default. Set SESSION_ENGINE=signed_cookies when +# pointing DB_ENGINE at a database whose user has read-only access, since the +# default engine writes a row to django_session on every login. +SESSION_ENGINE = ( + "django.contrib.sessions.backends.signed_cookies" + if os.environ.get("SESSION_ENGINE") == "signed_cookies" + else "django.contrib.sessions.backends.db" +) + # Password validation AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, diff --git a/standalone/wsgi.py b/standalone/wsgi.py index 7e8b218..0a4991b 100644 --- a/standalone/wsgi.py +++ b/standalone/wsgi.py @@ -7,3 +7,15 @@ from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "standalone.settings") application = get_wsgi_application() + +# Read-only deployments (SESSION_ENGINE=signed_cookies) point at a database +# whose user has no write access. Django writes to auth_user.last_login on +# every successful login by default, which would error out against such a +# user and break login entirely — so skip that write here. +from django.conf import settings # noqa: E402 + +if settings.SESSION_ENGINE == "django.contrib.sessions.backends.signed_cookies": + from django.contrib.auth.models import update_last_login + from django.contrib.auth.signals import user_logged_in + + user_logged_in.disconnect(update_last_login, dispatch_uid="update_last_login")