mirror of
https://github.com/RunLit/Bambu-Run.git
synced 2026-10-21 23:39:01 +01:00
Compare commits
5 Commits
fb1c3fb834
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e4108fb32 | ||
|
|
70731c637f | ||
|
|
4cf7d0114d | ||
|
|
6c16e66add | ||
|
|
6f681f8083 |
50
README.md
50
README.md
@@ -153,6 +153,46 @@ cd Bambu-Run
|
|||||||
bash setup.sh
|
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
|
## Docker Setup
|
||||||
@@ -304,7 +344,15 @@ Bambu-Run exposes printer status, print history, and filament inventory as
|
|||||||
read-only [MCP](https://modelcontextprotocol.io/) tools, so an AI agent (e.g.
|
read-only [MCP](https://modelcontextprotocol.io/) tools, so an AI agent (e.g.
|
||||||
Claude Desktop, Claude Code) can query your printer directly.
|
Claude Desktop, Claude Code) can query your printer directly.
|
||||||
|
|
||||||
Install the extra:
|
**Native install (`setup.sh`):** if you declined MCP during setup (or ran
|
||||||
|
`setup.sh` before MCP support existed), enable it any time with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./native/bambu-run.sh enable-mcp # install deps, create + start the systemd service
|
||||||
|
./native/bambu-run.sh disable-mcp # stop and remove it
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual / library install:** install the extra:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install 'bambu-run[mcp]'
|
pip install 'bambu-run[mcp]'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Type=exec
|
|||||||
WorkingDirectory={{REPO_DIR}}
|
WorkingDirectory={{REPO_DIR}}
|
||||||
EnvironmentFile={{REPO_DIR}}/.env
|
EnvironmentFile={{REPO_DIR}}/.env
|
||||||
Environment=DJANGO_SETTINGS_MODULE=standalone.settings
|
Environment=DJANGO_SETTINGS_MODULE=standalone.settings
|
||||||
ExecStart={{VENV_DIR}}/bin/python standalone/manage.py bambu_mcp_server --transport sse --host 0.0.0.0 --port 8808
|
ExecStart={{VENV_DIR}}/bin/python standalone/manage.py bambu_mcp_server --transport http --host 0.0.0.0 --port 8808
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
VENV_DIR="$REPO_DIR/.venv"
|
VENV_DIR="$REPO_DIR/.venv"
|
||||||
|
ENV_FILE="$REPO_DIR/.env"
|
||||||
MANAGE="$VENV_DIR/bin/python $REPO_DIR/standalone/manage.py"
|
MANAGE="$VENV_DIR/bin/python $REPO_DIR/standalone/manage.py"
|
||||||
SERVICES="bambu-run-web.service bambu-run-collector.service"
|
SERVICES="bambu-run-web.service bambu-run-collector.service"
|
||||||
|
|
||||||
@@ -37,6 +38,52 @@ case "${1:-help}" in
|
|||||||
fi
|
fi
|
||||||
journalctl --user $JOURNAL_UNITS -f --no-hostname
|
journalctl --user $JOURNAL_UNITS -f --no-hostname
|
||||||
;;
|
;;
|
||||||
|
enable-mcp)
|
||||||
|
if [ -f "$SERVICE_DIR/bambu-run-mcp.service" ]; then
|
||||||
|
echo "MCP server already enabled."
|
||||||
|
else
|
||||||
|
echo "Installing MCP dependencies..."
|
||||||
|
"$VENV_DIR/bin/pip" install --quiet ".[mcp]"
|
||||||
|
|
||||||
|
# Resolution order: a real process/systemd env var always wins and is
|
||||||
|
# never written to .env; otherwise reuse whatever's already in .env;
|
||||||
|
# otherwise generate one (same pattern setup.sh uses for DJANGO_SECRET_KEY).
|
||||||
|
if [ -n "${BAMBU_RUN_MCP_API_KEY:-}" ]; then
|
||||||
|
echo "Using BAMBU_RUN_MCP_API_KEY from the environment (not written to .env)."
|
||||||
|
elif grep -q '^BAMBU_RUN_MCP_API_KEY=' "$ENV_FILE" 2>/dev/null; then
|
||||||
|
echo "BAMBU_RUN_MCP_API_KEY already in .env, reusing it."
|
||||||
|
else
|
||||||
|
MCP_API_KEY=$("$VENV_DIR/bin/python" -c "import secrets; print(secrets.token_urlsafe(32))")
|
||||||
|
echo "BAMBU_RUN_MCP_API_KEY=$MCP_API_KEY" >> "$ENV_FILE"
|
||||||
|
echo "Generated BAMBU_RUN_MCP_API_KEY and saved it to .env."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed "s|{{REPO_DIR}}|$REPO_DIR|g; s|{{VENV_DIR}}|$VENV_DIR|g" \
|
||||||
|
"$REPO_DIR/native/bambu-run-mcp.service" > "$SERVICE_DIR/bambu-run-mcp.service"
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable bambu-run-mcp.service
|
||||||
|
systemctl --user start bambu-run-mcp.service
|
||||||
|
|
||||||
|
PI_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
MCP_KEY_FOR_DISPLAY="${BAMBU_RUN_MCP_API_KEY:-$(grep '^BAMBU_RUN_MCP_API_KEY=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-)}"
|
||||||
|
echo "MCP server enabled on port 8808."
|
||||||
|
echo
|
||||||
|
echo " Claude Desktop config:"
|
||||||
|
echo " {\"mcpServers\":{\"bambu-run\":{\"url\":\"http://${PI_IP:-localhost}:8808/mcp\",\"headers\":{\"Authorization\":\"Bearer ${MCP_KEY_FOR_DISPLAY}\"}}}}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
disable-mcp)
|
||||||
|
if [ ! -f "$SERVICE_DIR/bambu-run-mcp.service" ]; then
|
||||||
|
echo "MCP server is not enabled."
|
||||||
|
else
|
||||||
|
systemctl --user stop bambu-run-mcp.service 2>/dev/null || true
|
||||||
|
systemctl --user disable bambu-run-mcp.service 2>/dev/null || true
|
||||||
|
rm -f "$SERVICE_DIR/bambu-run-mcp.service"
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
echo "MCP server disabled."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
update)
|
update)
|
||||||
echo "Pulling latest code..."
|
echo "Pulling latest code..."
|
||||||
cd "$REPO_DIR" && git pull
|
cd "$REPO_DIR" && git pull
|
||||||
@@ -60,7 +107,7 @@ case "${1:-help}" in
|
|||||||
echo "Update complete."
|
echo "Update complete."
|
||||||
;;
|
;;
|
||||||
help|*)
|
help|*)
|
||||||
echo "Usage: $0 {start|stop|restart|status|logs|update}"
|
echo "Usage: $0 {start|stop|restart|status|logs|update|enable-mcp|disable-mcp}"
|
||||||
echo
|
echo
|
||||||
echo " start Start web + collector services"
|
echo " start Start web + collector services"
|
||||||
echo " stop Stop web + collector services"
|
echo " stop Stop web + collector services"
|
||||||
@@ -68,5 +115,7 @@ case "${1:-help}" in
|
|||||||
echo " status Show service status"
|
echo " status Show service status"
|
||||||
echo " logs Tail live logs (Ctrl+C to stop)"
|
echo " logs Tail live logs (Ctrl+C to stop)"
|
||||||
echo " update Pull latest code, install deps, migrate, restart"
|
echo " update Pull latest code, install deps, migrate, restart"
|
||||||
|
echo " enable-mcp Install and start the MCP server for AI agent access"
|
||||||
|
echo " disable-mcp Stop and remove the MCP server service"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
49
setup.sh
49
setup.sh
@@ -1,8 +1,21 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Bambu-Run Native Setup — single entry point for Raspberry Pi (or any Linux)
|
# Bambu-Run Native Setup — single entry point for Raspberry Pi (or any Linux)
|
||||||
# Usage: git clone ... && cd Bambu-Run && bash setup.sh
|
# Usage: git clone ... && cd Bambu-Run && bash setup.sh [-v|--verbose]
|
||||||
|
# -v, --verbose show full pip install output instead of --quiet
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERBOSE=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-v|--verbose) VERBOSE=1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
PIP_QUIET_FLAG="--quiet"
|
||||||
|
if [ "$VERBOSE" -eq 1 ]; then
|
||||||
|
PIP_QUIET_FLAG=""
|
||||||
|
fi
|
||||||
|
|
||||||
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
VENV_DIR="$REPO_DIR/.venv"
|
VENV_DIR="$REPO_DIR/.venv"
|
||||||
ENV_FILE="$REPO_DIR/.env"
|
ENV_FILE="$REPO_DIR/.env"
|
||||||
@@ -98,8 +111,8 @@ else:
|
|||||||
print(' opencv stub already exists')
|
print(' opencv stub already exists')
|
||||||
"
|
"
|
||||||
|
|
||||||
"$VENV_DIR/bin/pip" install --quiet --upgrade pip
|
"$VENV_DIR/bin/pip" install $PIP_QUIET_FLAG --upgrade pip
|
||||||
"$VENV_DIR/bin/pip" install --quiet ".[standalone]"
|
"$VENV_DIR/bin/pip" install $PIP_QUIET_FLAG ".[standalone]"
|
||||||
|
|
||||||
# ── 3. Interactive .env ───────────────────────────────────────────────────────
|
# ── 3. Interactive .env ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -205,10 +218,28 @@ systemctl --user enable bambu-run-web.service bambu-run-collector.service
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
MCP_ENABLED=false
|
MCP_ENABLED=false
|
||||||
read -rp "Enable MCP server for AI agent access (Claude Desktop, Claude Code, etc.)? [y/N] " ENABLE_MCP
|
if [ -f "$SERVICE_DIR/bambu-run-mcp.service" ]; then
|
||||||
if [[ "$ENABLE_MCP" =~ ^[Yy] ]]; then
|
yellow "MCP server already enabled, skipping (use ./native/bambu-run.sh disable-mcp first to reconfigure)."
|
||||||
|
MCP_ENABLED=true
|
||||||
|
else
|
||||||
|
read -rp "Enable MCP server for AI agent access (Claude Desktop, Claude Code, etc.)? [y/N] " ENABLE_MCP
|
||||||
|
if [[ "$ENABLE_MCP" =~ ^[Yy] ]]; then
|
||||||
green "Installing MCP dependencies..."
|
green "Installing MCP dependencies..."
|
||||||
"$VENV_DIR/bin/pip" install --quiet ".[mcp]"
|
"$VENV_DIR/bin/pip" install $PIP_QUIET_FLAG ".[mcp]"
|
||||||
|
|
||||||
|
# Resolution order: a real process/systemd env var always wins and is
|
||||||
|
# never written to .env (lets deployments override without touching
|
||||||
|
# the file); otherwise reuse whatever's already in .env; otherwise
|
||||||
|
# generate one, same pattern as DJANGO_SECRET_KEY above.
|
||||||
|
if [ -n "${BAMBU_RUN_MCP_API_KEY:-}" ]; then
|
||||||
|
yellow "Using BAMBU_RUN_MCP_API_KEY from the environment (not written to .env)."
|
||||||
|
elif grep -q '^BAMBU_RUN_MCP_API_KEY=' "$ENV_FILE" 2>/dev/null; then
|
||||||
|
yellow "BAMBU_RUN_MCP_API_KEY already in .env, reusing it."
|
||||||
|
else
|
||||||
|
MCP_API_KEY=$("$VENV_DIR/bin/python" -c "import secrets; print(secrets.token_urlsafe(32))")
|
||||||
|
echo "BAMBU_RUN_MCP_API_KEY=$MCP_API_KEY" >> "$ENV_FILE"
|
||||||
|
green "Generated BAMBU_RUN_MCP_API_KEY and saved it to .env."
|
||||||
|
fi
|
||||||
|
|
||||||
sed "s|{{REPO_DIR}}|$REPO_DIR|g; s|{{VENV_DIR}}|$VENV_DIR|g" \
|
sed "s|{{REPO_DIR}}|$REPO_DIR|g; s|{{VENV_DIR}}|$VENV_DIR|g" \
|
||||||
"$REPO_DIR/native/bambu-run-mcp.service" > "$SERVICE_DIR/bambu-run-mcp.service"
|
"$REPO_DIR/native/bambu-run-mcp.service" > "$SERVICE_DIR/bambu-run-mcp.service"
|
||||||
@@ -218,6 +249,7 @@ if [[ "$ENABLE_MCP" =~ ^[Yy] ]]; then
|
|||||||
systemctl --user start bambu-run-mcp.service
|
systemctl --user start bambu-run-mcp.service
|
||||||
MCP_ENABLED=true
|
MCP_ENABLED=true
|
||||||
green "MCP server enabled on port 8808."
|
green "MCP server enabled on port 8808."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Enable linger so services survive SSH logout
|
# Enable linger so services survive SSH logout
|
||||||
@@ -275,15 +307,16 @@ green "============================================"
|
|||||||
echo
|
echo
|
||||||
echo " Dashboard: $DASHBOARD_URL"
|
echo " Dashboard: $DASHBOARD_URL"
|
||||||
if [ "$MCP_ENABLED" = true ]; then
|
if [ "$MCP_ENABLED" = true ]; then
|
||||||
echo " MCP Server: http://${PI_IP:-localhost}:8808/sse"
|
echo " MCP Server: http://${PI_IP:-localhost}:8808/mcp"
|
||||||
fi
|
fi
|
||||||
echo " Status: systemctl --user status bambu-run-web bambu-run-collector"
|
echo " Status: systemctl --user status bambu-run-web bambu-run-collector"
|
||||||
echo " Logs: journalctl --user -u bambu-run-web -u bambu-run-collector -f"
|
echo " Logs: journalctl --user -u bambu-run-web -u bambu-run-collector -f"
|
||||||
echo " Helper: ./native/bambu-run.sh {start|stop|restart|status|logs|update}"
|
echo " Helper: ./native/bambu-run.sh {start|stop|restart|status|logs|update}"
|
||||||
echo
|
echo
|
||||||
if [ "$MCP_ENABLED" = true ]; then
|
if [ "$MCP_ENABLED" = true ]; then
|
||||||
|
MCP_KEY_FOR_DISPLAY="${BAMBU_RUN_MCP_API_KEY:-$(grep '^BAMBU_RUN_MCP_API_KEY=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-)}"
|
||||||
echo " Claude Desktop config:"
|
echo " Claude Desktop config:"
|
||||||
echo " {\"mcpServers\":{\"bambu-run\":{\"url\":\"http://${PI_IP:-localhost}:8808/sse\"}}}"
|
echo " {\"mcpServers\":{\"bambu-run\":{\"url\":\"http://${PI_IP:-localhost}:8808/mcp\",\"headers\":{\"Authorization\":\"Bearer ${MCP_KEY_FOR_DISPLAY}\"}}}}"
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
echo " Services auto-start on boot. Safe to close SSH."
|
echo " Services auto-start on boot. Safe to close SSH."
|
||||||
|
|||||||
@@ -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
|
# Password validation
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
@@ -124,7 +133,9 @@ PRINTER_IP = os.environ.get("PRINTER_IP", "")
|
|||||||
ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN", "")
|
ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN", "")
|
||||||
PRINTER_SERIAL = os.environ.get("PRINTER_SERIAL", "")
|
PRINTER_SERIAL = os.environ.get("PRINTER_SERIAL", "")
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",")
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
|
origin for origin in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if origin
|
||||||
|
]
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
|||||||
@@ -7,3 +7,15 @@ from django.core.wsgi import get_wsgi_application
|
|||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "standalone.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "standalone.settings")
|
||||||
|
|
||||||
application = get_wsgi_application()
|
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user