2d69c61556
gates / gates (push) Successful in 27s
The operator's request of 2026-10-01 recorded in CONTEXT with the reviewer's defaults (new apps only; the 53 get a read-only gap page). The pilot: the draft caught R-752 and R-755, missed R-737 and (for a new app) R-738; the sharpened and new rows then found R-762 (wger serves no static files or photos), R-763 (strangers sign up, guest accounts), R-764 (no mail). Also R-758 (8 mem_limit under the sum), R-759 (wger's open record rows), R-760 (vikunja healthcheck), R-761 (logo comment). R-755 note. Register 392 -> 399. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
143 lines
8.3 KiB
Python
143 lines
8.3 KiB
Python
#!/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)
|