scripts/real-print-p1p.sh queues a job in a throwaway one-printer farm and watches the machine print it. Refuses without I_AUTHORISE_A_REAL_PRINT=yes.
141 lines
8.2 KiB
Bash
Executable File
141 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# THE FIRST REAL DISPATCH: queue a job in a throwaway farm that holds only the
|
|
# P1P, let the real scheduler send it through the real proxy, and watch the
|
|
# machine print it.
|
|
#
|
|
# **This starts a physical print.** It refuses to run without
|
|
# I_AUTHORISE_A_REAL_PRINT=yes, and only ever targets PRINTER_ID's printer
|
|
# (default p1p) because the throwaway farm contains nothing else. The file is
|
|
# read from the printer's own SD card root (FILE, default
|
|
# auto-test-001.gcode.3mf), uploaded to the object store, and dispatched like
|
|
# any customer job; the proxy then downloads it, FTPS-uploads it to the card
|
|
# and starts it, mapping filament from the archive itself.
|
|
#
|
|
# Nothing here cancels the print. To stop it: the printer's own screen, or
|
|
# the console's Stop control.
|
|
set -euo pipefail
|
|
[ "${I_AUTHORISE_A_REAL_PRINT:-}" = "yes" ] || { echo "refusing: set I_AUTHORISE_A_REAL_PRINT=yes to start a real print" >&2; exit 2; }
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
PRINTER_ID="${PRINTER_ID:-p1p}"
|
|
FILE="${FILE:-auto-test-001.gcode.3mf}"
|
|
PROXY_DIR="${PROXY_DIR:-$REPO_ROOT/../continuum-proxy}"
|
|
GO2RTC_BIN="${GO2RTC_BIN:-$HOME/.local/bin/go2rtc}"
|
|
PORT="${PORT:-3060}"; AUTH_PORT="${AUTH_PORT:-3061}"
|
|
BASE="http://localhost:$PORT"
|
|
DATABASE_URL="${DATABASE_URL:-postgres://continuum:continuum@localhost:5432/continuum}"
|
|
VALKEY_URL="${VALKEY_URL:-redis://localhost:6379}"
|
|
EDGE_JWT_SECRET="${EDGE_JWT_SECRET:-e2e-edge-secret}"
|
|
export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_ENDPOINT
|
|
MAX_WAIT_S="${MAX_WAIT_S:-5400}"
|
|
|
|
WORK="$(mktemp -d)"
|
|
BACKEND_PID=""; PROXY_PID=""; AUTH_PID=""; OBJECT_KEY=""
|
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
ok() { echo " ok: $*"; }
|
|
psql_q() { psql "$DATABASE_URL" -tAq -c "$1"; }
|
|
USER_ID="$(cat /proc/sys/kernel/random/uuid)"
|
|
|
|
cleanup() {
|
|
local rc=$?
|
|
for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do [ -n "$pid" ] && kill "$pid" 2>/dev/null || true; done
|
|
for _ in $(seq 1 20); do [ -n "$PROXY_PID" ] && kill -0 "$PROXY_PID" 2>/dev/null || break; sleep 0.3; done
|
|
for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do [ -n "$pid" ] && kill -9 "$pid" 2>/dev/null || true; done
|
|
[ -n "$OBJECT_KEY" ] && bun -e "
|
|
import { s3 } from './src/s3';
|
|
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
await s3.send(new DeleteObjectCommand({ Bucket: process.env.R2_BUCKET ?? 'continuum', Key: process.argv[1] }));
|
|
" "$OBJECT_KEY" >/dev/null 2>&1 || true
|
|
[ -z "${KEEP_DATA:-}" ] && [ -n "${ORG_ID:-}" ] && psql_q "DELETE FROM organisations WHERE id = '$ORG_ID'" >/dev/null 2>&1 || true
|
|
if [ $rc -ne 0 ]; then
|
|
echo "--- backend log (tail) ---"; tail -25 "$WORK/backend.log" 2>/dev/null || true
|
|
echo "--- proxy log (tail) ---"; tail -40 "$WORK/proxy.log" 2>/dev/null || true
|
|
fi
|
|
rm -rf "$WORK"
|
|
exit $rc
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
[ -f "$PROXY_DIR/printers.toml" ] || fail "no printers.toml"
|
|
grep -q "^id *= *\"$PRINTER_ID\"" "$PROXY_DIR/printers.toml" || fail "printers.toml has no printer '$PRINTER_ID'"
|
|
ps -eo comm | grep -qx go2rtc && fail "a go2rtc is already running"
|
|
|
|
echo "==> reading $FILE from $PRINTER_ID's SD card (read-only)"
|
|
( cd "$PROXY_DIR" && cargo run -q --example bambu_mapping_check -- "$PRINTER_ID" "$FILE" "$WORK/job.3mf" ) > "$WORK/mapping.txt" 2>&1 || { cat "$WORK/mapping.txt"; fail "could not read the file"; }
|
|
grep -E 'gcode_state|project filament|mapping' "$WORK/mapping.txt" | sed 's/^/ /'
|
|
grep -q 'gcode_state: Some("IDLE")' "$WORK/mapping.txt" || grep -q 'gcode_state: Some("FINISH")' "$WORK/mapping.txt" || fail "the printer is not idle"
|
|
grep -q 'mapping the production path would use' "$WORK/mapping.txt" || fail "the filament would not map; not starting"
|
|
WANT="$(sha256sum "$WORK/job.3mf" | cut -d' ' -f1)"
|
|
|
|
echo "==> seeding a throwaway farm holding only $PRINTER_ID"
|
|
EDGE_PROXY_TOKEN="real-$(date +%s)"
|
|
ORG_ID="$(psql_q "INSERT INTO organisations (name) VALUES ('real print org') RETURNING id")"
|
|
psql_q "INSERT INTO org_members (org_id, user_id, role) VALUES ('$ORG_ID', '$USER_ID', 'owner')" >/dev/null
|
|
FARM_ID="$(psql_q "INSERT INTO farms (name, org_id, edge_proxy_token) VALUES ('real print farm', '$ORG_ID', '$EDGE_PROXY_TOKEN') RETURNING id")"
|
|
PRN_NAME="$(awk -F'"' -v id="$PRINTER_ID" '/^id *=/ {cur=$2} cur==id && /^name *=/ {print $2; exit}' "$PROXY_DIR/printers.toml")"
|
|
psql_q "INSERT INTO printers (farm_id, name, vendor, gateway_printer_id) VALUES ('$FARM_ID', '${PRN_NAME:-P1P}', 'bambu', '$PRINTER_ID')" >/dev/null
|
|
PRN_UUID="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID'")"
|
|
[ "$(psql_q "SELECT count(*) FROM printers WHERE farm_id='$FARM_ID'")" = 1 ] || fail "the farm must hold exactly one printer"
|
|
|
|
bun scripts/dev-auth.ts --port "$AUTH_PORT" > "$WORK/auth.log" 2>&1 &
|
|
AUTH_PID=$!
|
|
AUTH_URL="http://localhost:$AUTH_PORT"
|
|
for _ in $(seq 1 40); do curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 && break; sleep 0.25; done
|
|
export SUPABASE_URL="$AUTH_URL"
|
|
TOKEN="$(curl -sf "$AUTH_URL/token?sub=$USER_ID" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')"
|
|
echo "==> starting the backend WITH dispatch on"
|
|
DISPATCH_ENABLED=true bun run src/index.ts > "$WORK/backend.log" 2>&1 &
|
|
BACKEND_PID=$!
|
|
for _ in $(seq 1 60); do curl -sf "$BASE/health" >/dev/null 2>&1 && break; sleep 0.5; done
|
|
curl -sf "$BASE/health" >/dev/null || fail "backend did not become healthy"
|
|
|
|
echo "==> starting the real proxy"
|
|
env -C "$PROXY_DIR" \
|
|
CONTINUUM_GATEWAY_ID=real-print-gw CONTINUUM_FARM_ID="$FARM_ID" CONTINUUM_GATEWAY_TOKEN="$EDGE_PROXY_TOKEN" \
|
|
CONTINUUM_UPLINK_URL="ws://localhost:$PORT/ws/edge/v1" \
|
|
CONTINUUM_GO2RTC_BIN="$GO2RTC_BIN" CONTINUUM_GO2RTC_CONFIG="$WORK/go2rtc.yaml" \
|
|
CONTINUUM_PRINTERS_PATH=printers.toml CONTINUUM_STORE_PATH="$WORK/continuum.db" RUST_LOG=info \
|
|
"$PROXY_DIR/target/debug/continuum-proxy" > "$WORK/proxy.log" 2>&1 &
|
|
PROXY_PID=$!
|
|
for _ in $(seq 1 60); do
|
|
[ "$(psql_q "SELECT status FROM printers WHERE id='$PRN_UUID'")" = "idle" ] && break
|
|
sleep 1
|
|
done
|
|
[ "$(psql_q "SELECT status FROM printers WHERE id='$PRN_UUID'")" = "idle" ] || fail "the cloud never saw $PRINTER_ID idle"
|
|
ok "the cloud sees $PRINTER_ID idle"
|
|
|
|
echo "==> queueing the job"
|
|
J="$(curl -sf -X POST "$BASE/jobs" -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
|
|
-d "{\"farmId\":\"$FARM_ID\",\"fileName\":\"$FILE\",\"contentType\":\"application/octet-stream\"}")"
|
|
JOB_ID="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["id"])')"
|
|
OBJECT_KEY="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["r2ObjectKey"])')"
|
|
UPLOAD_URL="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["uploadUrl"])')"
|
|
# The job is queued the moment it exists; dispatch needs the bytes, so the
|
|
# window between the two is a race the scheduler can only lose (the proxy's
|
|
# download then fails and the job fails), which is why this is done at once.
|
|
CODE="$(curl -s -o /dev/null -w '%{http_code}' -X PUT -H 'Content-Type: application/octet-stream' --data-binary @"$WORK/job.3mf" "$UPLOAD_URL")"
|
|
[ "$CODE" = 200 ] || fail "upload returned HTTP $CODE"
|
|
ok "job $JOB_ID queued, $(stat -c %s "$WORK/job.3mf") bytes uploaded"
|
|
|
|
echo "==> watching (up to ${MAX_WAIT_S}s)"
|
|
SEEN=""; LAST=""; START=$SECONDS
|
|
while [ $((SECONDS - START)) -lt "$MAX_WAIT_S" ]; do
|
|
STATUS="$(psql_q "SELECT status FROM print_jobs WHERE id='$JOB_ID'")"
|
|
PRN="$(psql_q "SELECT coalesce(vendor_state,'-') || ' ' || coalesce(progress_percent::text,'-') || '% layer ' || coalesce(current_layer::text,'-') || '/' || coalesce(total_layers::text,'-') || ' nozzle ' || coalesce(nozzle_temp_c::text,'-') FROM printers WHERE id='$PRN_UUID'")"
|
|
NOW="job=$STATUS printer=$PRN"
|
|
[ "$NOW" != "$LAST" ] && { echo " [$((SECONDS - START))s] $NOW"; LAST="$NOW"; }
|
|
case " $SEEN " in *" $STATUS "*) ;; *) SEEN="$SEEN $STATUS" ;; esac
|
|
[ "$STATUS" = completed ] && break
|
|
[ "$STATUS" = failed ] && break
|
|
sleep 5
|
|
done
|
|
echo
|
|
echo "job status history:$SEEN"
|
|
if [ "$STATUS" = completed ]; then
|
|
grep -E 'uploaded a project file|mapped the project|start' "$WORK/proxy.log" | sed 's/^/ proxy: /' | head -8
|
|
echo "PASS - the first real dispatch: queued in the cloud, scheduled, uploaded to the P1P, printed and completed."
|
|
else
|
|
fail "the job ended as '$STATUS'"
|
|
fi
|