#!/usr/bin/env python3 """Part C, second short walk on 9202 (drill catalog): the three things the first walk's reads raised. 3.4 wger's settings read ALLOW_REGISTRATION=True and ALLOW_GUEST_USERS=True (C2): can a STRANGER make an account after the household's admin exists — by sign-up, and by "guest"? Through traefik, no dashboard session. 2.8 a photo uploaded through the API answered 201 and then 404 (C4): is the file on the volume, and who would serve /media/ — the cause, read inside the container. 1.7 the entrypoint runs collectstatic only when DJANGO_DEBUG == "False", and the template sets no DJANGO_DEBUG: does a static file answer? Then remove through the product. Secrets never printed. """ import io, json, os, re, sys, tempfile, time, secrets sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") import box_walk as w import upgrade_fixtures_box as fxb EV = os.environ["EV"] SUB = "fitness" OUT = [] def p(*a): line = " ".join(str(x) for x in a) print(line, flush=True) OUT.append(line) def dump(name): with io.open(os.path.join(EV, name), "w", encoding="utf-8") as fh: fh.write("\n".join(OUT) + "\n") w.login() p("##### wger second walk — controller", w.guest("docker inspect felhom-controller --format {{.Config.Image}}").strip(), "catalog", w.guest("cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache && git log --oneline -1").strip()) p("deploy ->", w.deploy("wger", SUB)) for _ in range(120): time.sleep(5) if "hold OPENED" in w.guest("docker logs --since 20m felhom-controller 2>&1 | grep 'wger: install hold OPENED'"): break w.wait_app(SUB, "/en/user/login", want=("200",), tries=72) pw = (w.GENERATED.get("wger") or {}).get("ADMIN_PASSWORD") or "" W = fxb.Wger() jar = tempfile.mktemp(prefix="wger-jar-") hdr, why = W._login(w, SUB, pw, jar) p("household admin signs in (the admin exists, the setup is done):", why) # ---- 3.4 a stranger signs up p("\n== 3.4 a STRANGER, after the setup — no dashboard session, no gate cookie, through traefik") p("wger source: where registration and guests are decided:") p(w.guest("docker exec wger sh -c \"grep -rn -E 'ALLOW_REGISTRATION|ALLOW_GUEST_USERS' /home/wger/src/wger --include=*.py | grep -v -E 'tests?/' | head -12\"").rstrip()) sj = tempfile.mktemp(prefix="wger-stranger-") o = f"https://{SUB}.{w.DOMAIN}" sh_ = ["-H", f"Origin: {o}", "-H", f"Referer: {o}/en/user/registration", "-c", sj, "-b", sj] rc, code, page = w.app_curl(SUB, "/en/user/registration", *sh_) fields = sorted(set(re.findall(r'name="([a-z_0-9]+)"', page or ""))) p("GET /en/user/registration ->", code, "form fields:", fields) m = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', page or "") uname = "stranger" + secrets.token_hex(3) spw = "Str-" + secrets.token_hex(8) if m: data = ["--data-urlencode", f"csrfmiddlewaretoken={m.group(1)}", "--data-urlencode", f"username={uname}", "--data-urlencode", f"email={uname}@example.invalid", "--data-urlencode", f"password1={spw}", "--data-urlencode", f"password2={spw}"] rc, code, body = w.app_curl(SUB, "/en/user/registration", *sh_, *data, method="POST") errs = re.findall(r'class="[^"]*(?:invalid-feedback|errorlist|alert-danger)[^"]*"[^>]*>\s*([^<]{3,120})', body or "") p(f"POST /en/user/registration as {uname} -> {code}; form errors: {errs[:3]}") sj2 = tempfile.mktemp(prefix="wger-stranger2-") sh2 = ["-H", f"Origin: {o}", "-H", f"Referer: {o}/en/user/login", "-c", sj2, "-b", sj2] rc, code, pg = w.app_curl(SUB, "/en/user/login", *sh2) m2 = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', pg or "") rc, code, _ = w.app_curl(SUB, "/en/user/login", *sh2, "--data-urlencode", f"csrfmiddlewaretoken={m2.group(1) if m2 else ''}", "--data-urlencode", f"login={uname}", "--data-urlencode", f"password={spw}", method="POST") p(f"the stranger then signs in with that account -> {code} ({'302 = IN' if code == '302' else 'refused'})") rc, code, out = w.app_curl(SUB, "/api/v2/weightentry/", *sh2) p(" and reads the API as that user ->", code) for f in (sj2,): os.path.exists(f) and os.unlink(f) p("the app's own user list now (admin's view, count only):", w.guest( "docker exec wger sh -c \"cd /home/wger/src && python3 manage.py shell -c 'from django.contrib.auth.models import User; print(User.objects.count(), sorted(u.username[:8] for u in User.objects.all()))'\" 2>/dev/null | tail -1").strip()) p("\n-- guests: wger's own 'try as guest' path, as a stranger") p(w.guest("docker exec wger sh -c \"grep -rn -E 'create_temporary_user|def demo_entries|guest' /home/wger/src/wger/core/urls.py /home/wger/src/wger/core/views/user.py /home/wger/src/wger/utils/*.py 2>/dev/null | head -12\"").rstrip()) gj = tempfile.mktemp(prefix="wger-guest-") gh = ["-c", gj, "-b", gj] for path in ("/en/user/demo-entries", "/en/dashboard"): rc, code, body = w.app_curl(SUB, path, *gh) p(f"GET {path} as a stranger -> {code}") p("users after the guest tries:", w.guest( "docker exec wger sh -c \"cd /home/wger/src && python3 manage.py shell -c 'from django.contrib.auth.models import User; print(User.objects.count(), sorted(u.username[:8] for u in User.objects.all()))'\" 2>/dev/null | tail -1").strip()) os.path.exists(gj) and os.unlink(gj) os.path.exists(sj) and os.unlink(sj) # ---- 2.8 the photo p("\n== 2.8 the uploaded photo: on the volume? who serves /media/?") import base64 png = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==") fn = tempfile.mktemp(suffix=".png") open(fn, "wb").write(png) rc, code, out = w.app_curl(SUB, "/api/v2/gallery/", *hdr, "-F", f"image=@{fn};type=image/png", "-F", "date=2026-10-01", "-F", "description=felhom pilot 2", method="POST") url = "" try: url = json.loads(out).get("image") or "" except Exception: pass path = re.sub(r"^https?://[^/]+", "", url) p("POST /api/v2/gallery/ ->", code, "image path:", path) p("the file on the media volume:", w.guest(f"docker exec wger ls -la /home/wger/media{path.replace('/media', '', 1)} 2>&1").strip()) rc, code, _ = w.app_curl(SUB, path, *hdr) p("GET it through traefik, signed in ->", code) p("inside the container, straight at runserver (no traefik):", w.guest(f"docker exec wger sh -c \"python3 -c \\\"import urllib.request as u;\ntry:\n print(u.urlopen('http://127.0.0.1:8000{path}').status)\nexcept Exception as e: print(e)\\\"\"").strip()) p("wger's urls.py on /media and /static:", w.guest("docker exec wger sh -c \"grep -n -E 'MEDIA|static\\(|serve' /home/wger/src/wger/urls.py | head -8\"").rstrip()) p("whitenoise / static middleware in settings:", w.guest("docker exec wger sh -c \"grep -n -i -E 'whitenoise|STATIC_ROOT|MEDIA_ROOT' /home/wger/src/settings/*.py /home/wger/src/wger/settings_global.py 2>/dev/null | head -8\"").rstrip()) p("upstream's own production compose serves /media with nginx — the image's docs:", w.guest("docker exec wger sh -c \"ls /home/wger/src/extras/docker/production 2>/dev/null; grep -rn -l 'location /media' /home/wger/src/extras 2>/dev/null | head -3\"").strip() or "(no extras/docker/production in the image)") os.unlink(fn) # ---- 1.7 a static file p("\n== 1.7 static files (collectstatic needs DJANGO_DEBUG == \"False\"; the template sets none)") rc, code, body = w.app_curl(SUB, "/en/user/login") css = re.findall(r'(?:href|src)="(/static/[^"]+\.(?:css|js))"', body or "") p("static refs on the login page:", css[:3]) for c in css[:2]: rc, code, b = w.app_curl(SUB, c) p(f"GET {c} -> {code} ({len(b)} chars)") p("static root inside the container:", w.guest("docker exec wger sh -c 'ls /home/wger/static 2>&1 | head -5; du -sh /home/wger/static 2>/dev/null'").strip()) dump("C8-signup-guest-media-static.txt") # ---- remove OUT.clear() c1, _ = w.ctl("POST", "/api/stacks/wger/stop") for _ in range(24): time.sleep(5) if w.stack("wger").get("state") != "running": break code, d = w.ctl("POST", "/api/stacks/wger/remove", {"remove_hdd_data": True, "remove_backups": True}) p("remove WITH data ->", code, str(d)[:200]) time.sleep(6) p("left after: stack dir / containers / volumes:", w.guest( "ls -d /opt/docker/stacks/wger 2>/dev/null; docker ps -a --format '{{.Names}}' | grep -x wger; docker volume ls -q | grep -i wger").strip() or "nothing") dump("C8b-remove-with-data.txt") os.path.exists(jar) and os.unlink(jar)