New-app checklist: the pilot audit (wger as of 2026-09-29 and now, two 9202 walks), R-758..R-764 opened; STATUS, CONTEXT, report
gates / gates (push) Successful in 27s
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
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
|
||||
ok FACT: n/a with a two-word reason rc=1 (expected 1)
|
||||
ok FACT: an OPEN row rc=1 (expected 1)
|
||||
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
|
||||
catalog gate decoys OK — 121 case(s), every label judged on its fact (R-421)
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
|
||||
ok FACT: n/a with a two-word reason rc=1 (expected 1)
|
||||
ok FACT: an OPEN row rc=1 (expected 1)
|
||||
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
MUTANT no comment stripping -> suite RED (1 case(s) failed)
|
||||
FACT: 1.4 answered only inside an HTML comment: rc=0 expected 1; missing ['missing id(s): 1.4']
|
||||
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
|
||||
ok FACT: n/a with a two-word reason rc=1 (expected 1)
|
||||
ok FACT: an OPEN row rc=1 (expected 1)
|
||||
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
MUTANT exists() instead of non-empty evidence -> suite RED (1 case(s) failed)
|
||||
FACT: done with an EMPTY directory (the mkdir shape): rc=0 expected 1; missing ['id 5.1 is done but its evidence']
|
||||
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
|
||||
ok FACT: n/a with a two-word reason rc=1 (expected 1)
|
||||
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
MUTANT open rows ignored -> suite RED (1 case(s) failed)
|
||||
FACT: an OPEN row: rc=0 expected 1; missing ['id 6.3 is OPEN']
|
||||
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: an OPEN row rc=1 (expected 1)
|
||||
ok FACT: opened: backdated before the checklist rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
MUTANT no reason length -> suite RED (2 case(s) failed)
|
||||
FACT: n/a with an EMPTY reason: rc=0 expected 1; missing ['id 7.1 is n/a']
|
||||
FACT: n/a with a two-word reason: rc=0 expected 1; missing ['id 7.1 is n/a']
|
||||
|
||||
-- onboarding: the facts (each MUST be refused)
|
||||
ok FACT: a new template with NO record rc=1 (expected 1)
|
||||
ok FACT: a record missing id 1.4 rc=1 (expected 1)
|
||||
ok FACT: 1.4 answered only inside an HTML comment rc=1 (expected 1)
|
||||
ok FACT: done with a path that does not exist rc=1 (expected 1)
|
||||
ok FACT: done with an EMPTY directory (the mkdir shape) rc=1 (expected 1)
|
||||
ok FACT: done naming an absent file in the sibling repo rc=1 (expected 1)
|
||||
ok FACT: n/a with an EMPTY reason rc=1 (expected 1)
|
||||
ok FACT: n/a with a two-word reason rc=1 (expected 1)
|
||||
ok FACT: an OPEN row rc=1 (expected 1)
|
||||
ok FACT: a checklist id the template a new app copies lacks rc=1 (expected 1)
|
||||
ok FACT: an exempt app's record with a done that points nowhere rc=1 (expected 1)
|
||||
-- onboarding: the genuine articles (each MUST pass)
|
||||
ok GENUINE: a complete record (catalog + sibling evidence) rc=0 (expected 0)
|
||||
ok GENUINE: an id added AFTER opened: does not bind rc=0 (expected 0)
|
||||
ok GENUINE: an exempt app's record may say open rc=0 (expected 0)
|
||||
ok STATED SKIP: sibling repo absent (the CI shape) - printed, not checked rc=0 (expected 0)
|
||||
MUTANT no cutoff check -> suite RED (1 case(s) failed)
|
||||
FACT: opened: backdated before the checklist: rc=0 expected 1; missing ['before the checklist existed']
|
||||
@@ -0,0 +1,35 @@
|
||||
# upstream reads for wger, 2026-10-01T13:07:39Z (read only: GitHub API + Docker Hub API, anonymous)
|
||||
repo: wger-project/wger licence: AGPL-3.0 pushed_at: 2026-10-01T02:03:43Z open_issues: 251 archived: False
|
||||
release: 2.7 2026-09-03T09:33:59Z
|
||||
release: 2.6 2026-06-17T07:47:47Z
|
||||
release: 2.5 2026-04-15T20:09:19Z
|
||||
release: 2.4 2026-01-18T12:12:02Z
|
||||
release: 2.3 2025-04-05T18:05:36Z
|
||||
tag: latest 2026-09-29 archs: ['amd64', 'arm64'] size_amd64_MB: [374]
|
||||
tag: 2.8.0-dev.0 2026-09-29 archs: ['amd64', 'arm64'] size_amd64_MB: [374]
|
||||
tag: 2.7 2026-09-06 archs: ['amd64', 'arm64'] size_amd64_MB: [375]
|
||||
tag: 2.7.0 2026-09-06 archs: ['amd64', 'arm64'] size_amd64_MB: [375]
|
||||
tag: 2.7.0-alpha2 2026-09-02 archs: ['amd64', 'arm64'] size_amd64_MB: [375]
|
||||
tag: 2.7.0-alpha1 2026-08-11 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [382]
|
||||
tag: 2.6.0 2026-06-17 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [373]
|
||||
tag: 2.6 2026-06-17 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [373]
|
||||
tag: 2.6.0-alpha2 2026-06-17 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [372]
|
||||
tag: 2.6-dev 2026-05-13 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [371]
|
||||
tag: 2.5 2026-04-15 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [370]
|
||||
tag: 2.5-dev 2026-04-15 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [370]
|
||||
tag: 2.4-dev 2026-01-20 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [270]
|
||||
tag: 2.4 2026-01-18 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [270]
|
||||
tag: 2.3-dev 2025-04-05 archs: ['amd64', 'arm', 'arm64', 'unknown'] size_amd64_MB: [152]
|
||||
tag: routines-beta 2025-03-14 archs: ['amd64', 'arm64', 'unknown'] size_amd64_MB: [247]
|
||||
tag: routines 2024-11-19 archs: ['arm64'] size_amd64_MB: []
|
||||
tag: 2.2-dev 2023-12-05 archs: ['amd64', 'arm', 'arm64', 'unknown'] size_amd64_MB: [262]
|
||||
tag: master 2023-08-07 archs: ['amd64', 'arm', 'arm64', 'unknown'] size_amd64_MB: [251]
|
||||
tag: 2.1-dev 2022-10-12 archs: ['amd64', 'arm64'] size_amd64_MB: [221]
|
||||
--- wger Flutter app (phone): repo
|
||||
wger-project/flutter 2026-09-29T03:14:21Z
|
||||
--- felhom.eu assets
|
||||
https://felhom.eu/assets/wger-logo.webp -> 404 153B
|
||||
https://felhom.eu/assets/wger-logo.png -> 200
|
||||
https://felhom.eu/assets/wger-screenshot-1.webp -> 200
|
||||
https://felhom.eu/assets/paperless-ngx-logo.svg -> 200
|
||||
https://felhom.eu/assets/nosuchapp-logo.png -> 404
|
||||
@@ -0,0 +1,11 @@
|
||||
== check-image-pins.py (catalog d0e7e2e, 2026-09-29)
|
||||
image-pin gate OK — 53 templates, 0 unpinned images
|
||||
gate exit code = 0
|
||||
== check-probe-matches-compose.py (catalog d0e7e2e, 2026-09-29)
|
||||
|
||||
probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials
|
||||
gate exit code = 0
|
||||
== check-copy-i18n.py (catalog d0e7e2e, 2026-09-29)
|
||||
copy-i18n: translated so far — actualbudget 15/15, adventurelog 17/17, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 21/21, calibre-web 25/25, claper 20/20, code-server 20/20, crafty
|
||||
copy-i18n: OK
|
||||
gate exit code = 0
|
||||
@@ -0,0 +1,22 @@
|
||||
# wger, catalog 6d72c09 (= live main), 2026-10-01T13:16:34Z
|
||||
== check-image-pins.py
|
||||
image-pin gate OK — 53 templates, 0 unpinned images
|
||||
gate exit code = 0
|
||||
== check-image-resolvable.py wger
|
||||
resolving 1 unique image pin(s)…
|
||||
image-resolvability gate OK — 1 unique pins, all resolve
|
||||
gate exit code = 0
|
||||
== check-probe-matches-compose.py wger
|
||||
|
||||
probe-matches-compose: OK — every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials
|
||||
gate exit code = 0
|
||||
== check-probe-measured.py wger
|
||||
probe-measured: OK — 0 probes, each with a measured before/after
|
||||
gate exit code = 0
|
||||
== check-copy-i18n.py wger
|
||||
copy-i18n: translated so far — actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, d
|
||||
copy-i18n: OK
|
||||
gate exit code = 0
|
||||
== check-test-record.py wger
|
||||
test-record gate — 1 template(s) read, 1 carry a ladder, 0 convicted
|
||||
gate exit code = 0
|
||||
@@ -0,0 +1,125 @@
|
||||
# =============================================================================
|
||||
# .felhom.yml - App metadata for felhom-controller
|
||||
# =============================================================================
|
||||
|
||||
# --- Display info (shown on dashboard) ---
|
||||
display_name: "wger"
|
||||
description: "Edzésnapló és fitnesz tervező"
|
||||
category: "home"
|
||||
subdomain: "fitness"
|
||||
slug: "wger"
|
||||
# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that
|
||||
# changes an image: line must set this to the same day (see CLAUDE.md).
|
||||
catalog_since: "2026-07-19"
|
||||
|
||||
# --- Resource hints (displayed on deploy screen) ---
|
||||
resources:
|
||||
mem_request: "100M"
|
||||
mem_limit: "384M"
|
||||
pi_compatible: true
|
||||
needs_hdd: false
|
||||
|
||||
# --- Deploy fields (first deployment only) ---
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: "Domain"
|
||||
type: domain
|
||||
description: "A szerver domain neve"
|
||||
locked_after_deploy: true
|
||||
|
||||
- env_var: SUBDOMAIN
|
||||
label: "Aldomain"
|
||||
type: subdomain
|
||||
default: "fitness"
|
||||
required: true
|
||||
locked_after_deploy: true
|
||||
description: "Az alkalmazás aldomainje"
|
||||
|
||||
- env_var: SECRET_KEY
|
||||
label: "Titkosítási kulcs"
|
||||
type: secret
|
||||
generate: "hex:32"
|
||||
locked_after_deploy: true
|
||||
|
||||
# `09` §3 decision 45: wger starts with admin / adminadmin. The box replaces that password with this generated
|
||||
# one right after the install (after_install below); the app page shows it as the first password.
|
||||
- env_var: ADMIN_PASSWORD
|
||||
label: "Admin jelszó (admin)"
|
||||
type: password
|
||||
generate: "password:24"
|
||||
description: "Az első bejelentkezéshez: admin és ez a jelszó. Utána a beállításokban módosítható."
|
||||
locked_after_deploy: true
|
||||
|
||||
# --- App info (info page content) ---
|
||||
app_info:
|
||||
tagline: "Edzésnapló - edzéstervek, haladás követés és testsúly napló"
|
||||
default_creds: "admin / adminadmin"
|
||||
docs_url: "https://wger.readthedocs.io/"
|
||||
|
||||
use_cases:
|
||||
- 'Edzéstervek létrehozása és követése'
|
||||
- 'Testsúly és testméretek nyilvántartása grafikonokkal'
|
||||
- 'Gyakorlatok adatbázisa képekkel és leírásokkal'
|
||||
- 'Kalória és tápanyag követés'
|
||||
- 'API támogatás fitnesz alkalmazás integrációkhoz'
|
||||
|
||||
first_steps:
|
||||
- 'Nyisd meg a fitness.DOMAIN címet a böngészőben'
|
||||
- 'Jelentkezz be: admin és a Beállítások oldalon látható első jelszó'
|
||||
- 'Add meg az email címedet a beállításokban'
|
||||
- 'Hozd létre az edzéstervedet'
|
||||
- 'Kezdd el naplózni az edzéseidet'
|
||||
|
||||
|
||||
# --- After a fresh install (controller >= 0.279.0, decision 45) ---
|
||||
# Django's own set_password on the seeded admin. Measured on 9202 2026-09-29: afterwards adminadmin no longer signs in
|
||||
# at /en/user/login, the new one does.
|
||||
after_install:
|
||||
service: wger
|
||||
env: [ADMIN_PASSWORD]
|
||||
# The password is the LAST ARGUMENT (sys.argv[1]), never pasted into the code: a quote in it cannot break or change
|
||||
# the program (security review 2026-09-29). Django is set up the way manage.py does it (settings.main).
|
||||
command: ["python3", "-c", "import os, sys; sys.path.insert(0, '/home/wger/src'); os.chdir('/home/wger/src'); os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main'); import django; django.setup(); from django.contrib.auth.models import User; u = User.objects.get(username='admin'); u.set_password(sys.argv[1]); u.save(); print('FELHOM_AFTER_INSTALL_OK')", "${ADMIN_PASSWORD}"]
|
||||
success: "FELHOM_AFTER_INSTALL_OK"
|
||||
|
||||
# --- Controller-side health probe ---
|
||||
healthcheck:
|
||||
checks:
|
||||
- type: http
|
||||
# 8000, not 80: gunicorn listens on 8000 inside the container; 80 is only
|
||||
# what traefik publishes. wger's own compose healthcheck dials 127.0.0.1:8000 (R-618).
|
||||
port: 8000
|
||||
|
||||
# --- English copy (localisation slice 5, R-560) --------------------------------------------
|
||||
# The Hungarian above is UNCHANGED. A box on English reads this block field by field; a missing
|
||||
# field shows the Hungarian one; a controller older than 0.257.0 ignores the block entirely.
|
||||
i18n:
|
||||
en:
|
||||
description: 'A workout log and fitness planner'
|
||||
app_info:
|
||||
tagline: 'A workout log - training plans, progress and a weight diary'
|
||||
default_creds: 'admin / adminadmin'
|
||||
use_cases:
|
||||
- 'Build training plans and follow them'
|
||||
- 'Track your weight and measurements on a graph'
|
||||
- 'An exercise database with pictures and descriptions'
|
||||
- 'Track calories and nutrition'
|
||||
- 'An API, for fitness app integrations'
|
||||
first_steps:
|
||||
- 'Open fitness.DOMAIN in your browser'
|
||||
- 'Sign in: admin and the first password shown on the settings page'
|
||||
- 'Add your e-mail address in the settings'
|
||||
- 'Build your training plan'
|
||||
- 'Start logging your workouts'
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: 'Domain'
|
||||
description: 'The server domain name'
|
||||
- env_var: SUBDOMAIN
|
||||
label: 'Subdomain'
|
||||
description: 'The subdomain this app answers on'
|
||||
- env_var: SECRET_KEY
|
||||
label: 'Encryption key'
|
||||
- env_var: ADMIN_PASSWORD
|
||||
label: 'Admin password (admin)'
|
||||
description: 'For the first sign-in: admin and this password. Change it in the settings afterwards.'
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# wger - Edzésnapló és fitnesz tervező
|
||||
# Domain: ${SUBDOMAIN}.${DOMAIN}
|
||||
# Database: None (file-based)
|
||||
# RAM: ~100M (mem_limit: 384M) | Pi-compatible: Yes
|
||||
#
|
||||
# Environment variables:
|
||||
# DOMAIN - Your domain (e.g., demo-felhom.eu)
|
||||
# SECRET_KEY - Titkosítási kulcs (auto-generated)
|
||||
|
||||
services:
|
||||
wger:
|
||||
image: wger/server:2.6
|
||||
container_name: wger
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
# A wger 2.4+ a TELJES DJANGO_DB_* halmazt beolvassa, akkor is, ha az
|
||||
# engine sqlite -- enélkül indulás nélkül kilép ("Set the DJANGO_DB_USER
|
||||
# environment variable"). Az USER/PASSWORD/HOST/PORT értékeket az sqlite
|
||||
# backend figyelmen kívül hagyja, de jelen kell lenniük.
|
||||
# A DATABASE a wger_data kötetre mutat (/home/wger/db), oda, ahol a wger
|
||||
# saját alapértelmezett sqlite fájlja is volt -- így meglévő telepítés
|
||||
# adatai nem "tűnnek el" egy másik útvonalra.
|
||||
# R-712 (measured 2026-09-29 on 9202): behind traefik wger saw the request as http and refused a browser's
|
||||
# https Origin with "CSRF verification failed" — nobody could sign in from a browser.
|
||||
- CSRF_TRUSTED_ORIGINS=https://${SUBDOMAIN}.${DOMAIN}
|
||||
- X_FORWARDED_PROTO_HEADER_SET=True
|
||||
- DJANGO_DB_ENGINE=django.db.backends.sqlite3
|
||||
- DJANGO_DB_DATABASE=/home/wger/db/database.sqlite
|
||||
- DJANGO_DB_USER=wger
|
||||
- DJANGO_DB_PASSWORD=wger
|
||||
- DJANGO_DB_HOST=localhost
|
||||
- DJANGO_DB_PORT=5432
|
||||
- SITE_URL=https://${SUBDOMAIN}.${DOMAIN}
|
||||
volumes:
|
||||
- wger_data:/home/wger/db
|
||||
- wger_media:/home/wger/media
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 384M
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8000"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.wger.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)"
|
||||
- "traefik.http.routers.wger.entrypoints=websecure"
|
||||
- "traefik.http.routers.wger.tls=true"
|
||||
- "traefik.http.routers.wger.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.wger.loadbalancer.server.port=8000"
|
||||
|
||||
volumes:
|
||||
wger_data:
|
||||
wger_media:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -0,0 +1,14 @@
|
||||
saved copy: 1944 /var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml.pre-checklist1001
|
||||
git:
|
||||
branch: main
|
||||
repo_url: https://gitea.dooplex.hu/admin/app-catalog-drill.git
|
||||
sync_interval: 15m
|
||||
token: <redacted>
|
||||
username: "admin"
|
||||
hub:
|
||||
|
||||
CONTROL 1 — the box's catalog clone:
|
||||
CONTROL 2 — the LIVE catalog main (unchanged by this act): ['6d72c091e039197758bf999ef8154b13530482b1']
|
||||
CONTROL 3 — expected source: drill
|
||||
e9f50b5 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
|
||||
https://gitea.dooplex.hu/admin/app-catalog-drill.git
|
||||
@@ -0,0 +1,15 @@
|
||||
##### wger on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.285.0
|
||||
catalog clone: e9f50b5 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
|
||||
before: stack/volumes named wger: /opt/docker/stacks/wger
|
||||
guest swap (free -m): Swap: 512 19 492
|
||||
sampler started
|
||||
poller started
|
||||
deploy press at 15:06:36
|
||||
deploy -> True
|
||||
controller log (after_install / hold): 2026/10/01 13:08:09 install_hold.go:135: [INFO] [stacks] wger: install hold OPENED by after_install — the app is reached as without a hold
|
||||
4.3 press -> docker healthy: 107 s; RestartCount: 0; start_period in compose: start_period: 30s
|
||||
controller state: running
|
||||
3.5 stranger polls of admin/adminadmin from the press: 92 tries; codes {'404': 90, '401': 1, '400': 1}
|
||||
first 3: ['13:06:35 404 404 page not found ', '13:06:36 404 404 page not found ', '13:06:37 404 404 page not found ']
|
||||
last 3: ['13:08:07 404 404 page not found ', '13:08:08 401 {"error":"this app is waiting for its first setup"}', '13:08:10 400 {"status": 400, "errors": [{"message": "The username and/or password you specified are not']
|
||||
any 200 with a token: False
|
||||
@@ -0,0 +1,48 @@
|
||||
1.5 the server process: python3 manage.py runserver 0.0.0.0:8000 | /usr/bin/python3 manage.py runserver 0.0.0.0:8000
|
||||
1.7 image entrypoint:
|
||||
1.7 entrypoint switches (if-tests in /home/wger/entrypoint.sh):
|
||||
10:if [ "$YARN_PROCESS_STATIC" == "True" ];
|
||||
21:if [ "$DJANGO_CLEAR_STATIC_FIRST" == "False" ]; then
|
||||
27:if [[ "$DJANGO_DEBUG" == "False" && "${DJANGO_COLLECTSTATIC_ON_STARTUP:-True}" == "True" ]];
|
||||
34:if [[ "$DJANGO_PERFORM_MIGRATIONS" == "True" ]];
|
||||
41:if [[ "$SYNC_EXERCISES_ON_STARTUP" == "True" ]];
|
||||
48:if [[ "$DOWNLOAD_EXERCISE_IMAGES_ON_STARTUP" == "True" ]];
|
||||
55:if [[ "$DOWNLOAD_EXERCISE_VIDEOS_ON_STARTUP" == "True" ]];
|
||||
62:if [[ "$LOAD_ONLINE_FIXTURES_ON_STARTUP" == "True" ]];
|
||||
69:if [[ "$SYNC_INGREDIENTS_ON_STARTUP" == "True" ]];
|
||||
81:if [[ "$WGER_USE_GUNICORN" == "True" ]];
|
||||
0.4/1.7 the IMAGE's env defaults for those switches (+ debug/mail/sync):
|
||||
the RUNNING container's env, the same switches (values: only these named, non-secret ones):
|
||||
container: SITE_URL=https://fitness.enkisfelhom.hu
|
||||
container: AXES_COOLOFF_TIME=5
|
||||
container: DJANGO_PERFORM_MIGRATIONS=True
|
||||
container: AXES_HANDLER=axes.handlers.database.AxesDatabaseHandler
|
||||
container: AXES_LOCKOUT_PARAMETERS=username
|
||||
container: X_FORWARDED_PROTO_HEADER_SET=True
|
||||
container: TZ=Europe/Budapest
|
||||
container: CSRF_TRUSTED_ORIGINS=https://fitness.enkisfelhom.hu
|
||||
container env NAMES (all): APP_BUILD_COMMIT APP_BUILD_DATE AXES_COOLOFF_TIME AXES_HANDLER AXES_LOCKOUT_PARAMETERS CSRF_TRUSTED_ORIGINS DEBIAN_FRONTEND DJANGO_DB_DATABASE DJANGO_DB_ENGINE DJANGO_DB_HOST DJANGO_DB_PASSWORD DJANGO_DB_PORT DJANGO_DB_USER DJANGO_PERFORM_MIGRATIONS DJANGO_SETTINGS_MODULE LANG LANGUAGE LC_ALL PATH PYTHONDONTWRITEBYTECODE PYTHONPATH PYTHONUNBUFFERED SECRET_KEY SITE_URL TZ X_FORWARDED_PROTO_HEADER_SET
|
||||
1.6 the env names wger's settings READ (settings/main.py, env.*('NAME'...)):
|
||||
ACCESS_TOKEN_LIFETIME ALLOW_GUEST_USERS ALLOW_REGISTRATION ALLOW_UPLOAD_VIDEOS AUTH_PROXY_CREATE_UNKNOWN_USER AUTH_PROXY_HEADER AUTH_PROXY_TRUSTED_IPS AUTH_PROXY_USER_EMAIL_HEADER AUTH_PROXY_USER_NAME_HEADER AWS_ACCESS_KEY_ID AWS_S3_DOMAIN AWS_S3_REGION_NAME AWS_SECRET_ACCESS_KEY AWS_STORAGE_BUCKET_NAME AXES_COOLOFF_TIME AXES_ENABLED AXES_FAILURE_LIMIT AXES_HANDLER AXES_IPWARE_PROXY_COUNT AXES_LOCKOUT_PARAMETERS CACHE_API_EXERCISES_CELERY CELERY_BACKEND CELERY_BROKER DJANGO_ADMINS DJANGO_CACHE_BACKEND DJANGO_CACHE_CLIENT_CLASS DJANGO_CACHE_CLIENT_PASSWORD DJANGO_CACHE_CLIENT_SSL_CERTFILE DJANGO_CACHE_CLIENT_SSL_CERT_REQS DJANGO_CACHE_CLIENT_SSL_KEYFILE DJANGO_CACHE_LOCATION DJANGO_CACHE_TIMEOUT DJANGO_DB_DATABASE DJANGO_DB_ENGINE DJANGO_DB_HOST DJANGO_DB_PASSWORD DJANGO_DB_PORT DJANGO_DB_USER DJANGO_MEDIA_ROOT DJANGO_STATIC_ROOT DOWNLOAD_INGREDIENTS_FROM EMAIL_HOST EMAIL_HOST_PASSWORD EMAIL_HOST_USER EMAIL_PORT EMAIL_USE_SSL EMAIL_USE_TLS ENABLE_EMAIL EXERCISE_CACHE_TTL EXPORT_INGREDIENTS_BULK_CELERY EXPOSE_PROMETHEUS_METRICS FROM_EMAIL IDP_OIDC_PRIVATE_KEY JWT_PRIVATE_KEY JWT_PUBLIC_KEY LOGIN_REDIRECT_URL LOG_LEVEL_PYTHON MEDIA_URL MIN_ACCOUNT_AGE_TO_TRUST NUMBER_OF_PROXIES POWERSYNC_TOKEN_LIFETIME POWERSYNC_URL POWERSYNC_URL_PATH RECAPTCHA_PRIVATE_KEY RECAPTCHA_PUBLIC_KEY RECAPTCHA_REQUIRED_SCORE REFRESH_TOKEN_LIFETIME S3_MEDIA_FILES_LOCATION S3_STATIC_FILES_LOCATION SECRET_KEY SECURE_PROXY_SSL_HEADER SITE_URL STATIC_URL SYNC_EXERCISE_IMAGES_CELERY SYNC_EXERCISES_CELERY SYNC_EXERCISE_VIDEOS_CELERY SYNC_INGREDIENTS_CELERY SYNC_OFF_DAILY_DELTA_CELERY TIME_ZONE USE_CELERY USE_RECAPTCHA USE_S3_MEDIA_FILES USE_S3_STATIC_FILES USE_S3_URL_FOR_MEDIA USE_S3_URL_FOR_STATIC USE_X_FORWARDED_HOST WGER_MAX_SESSION_LENGTH_HOURS WGER_SHOW_APP_STORE_LINKS WGER_SOCIAL_PROVIDERS X_FORWARDED_PROTO_HEADER_SET
|
||||
of which key/secret-like: ['ACCESS_TOKEN_LIFETIME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'DJANGO_CACHE_CLIENT_PASSWORD', 'DJANGO_CACHE_CLIENT_SSL_KEYFILE', 'DJANGO_DB_PASSWORD', 'EMAIL_HOST_PASSWORD', 'IDP_OIDC_PRIVATE_KEY', 'JWT_PRIVATE_KEY', 'JWT_PUBLIC_KEY', 'POWERSYNC_TOKEN_LIFETIME', 'RECAPTCHA_PRIVATE_KEY', 'RECAPTCHA_PUBLIC_KEY', 'REFRESH_TOKEN_LIFETIME', 'SECRET_KEY']
|
||||
set in the container: ['DJANGO_DB_PASSWORD', 'SECRET_KEY'] NOT set: ['ACCESS_TOKEN_LIFETIME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'DJANGO_CACHE_CLIENT_PASSWORD', 'DJANGO_CACHE_CLIENT_SSL_KEYFILE', 'EMAIL_HOST_PASSWORD', 'IDP_OIDC_PRIVATE_KEY', 'JWT_PRIVATE_KEY', 'JWT_PUBLIC_KEY', 'POWERSYNC_TOKEN_LIFETIME', 'RECAPTCHA_PRIVATE_KEY', 'RECAPTCHA_PUBLIC_KEY', 'REFRESH_TOKEN_LIFETIME']
|
||||
1.8/0.4 Django settings inside the app:
|
||||
DEBUG False
|
||||
EMAIL_BACKEND django.core.mail.backends.console.EmailBackend
|
||||
WGER_SETTINGS sync/download: {'ALLOW_GUEST_USERS': True, 'ALLOW_REGISTRATION': True, 'ALLOW_UPLOAD_VIDEOS': True, 'EMAIL_FROM': 'wger Workout Manager <wger@example.com>', 'DOWNLOAD_INGREDIENTS_FROM': 'WGER', 'SYNC_EXERCISES_CELERY': False, 'SYNC_EXERCISE_IMAGES_CELERY': False, 'SYNC_EXERCISE_VIDEOS_CELERY': False, 'SYNC_INGREDIENTS_CELERY': False, 'SYNC_OFF_DAILY_DELTA_CELERY': False, 'SYNC_INGREDIENTS_DUMP_URL': 'https://wger.de/media/ingredients/ingredients.jsonl.gz', 'TWITTER': False, 'MASTODON': 'https://fosstodon.org/@wger', 'USE_CELERY': False}
|
||||
JWT key loaded: False (env in this shell is not the server env)
|
||||
0.4/0.5 the container's first-start log lines about the network (sync/download/http):
|
||||
Apply all migrations: account, actstream, allauth_idp_oidc, auth, authtoken, axes, config, contenttypes, core, easy_thumbnails, exercises, gallery, gym, mailer, manager, measurements, mfa, nutrition, sessions, sites, socialaccount, token_blacklist, trophies, weight
|
||||
Applying nutrition.0012_alter_ingredient_license_author... OK
|
||||
Applying exercises.0001_initial... OK
|
||||
Applying nutrition.0013_ingredient_image... OK
|
||||
Applying nutrition.0015_alter_ingredient_creation_date_and_more... OK
|
||||
Applying nutrition.0024_remove_ingredient_status... OK
|
||||
Applying nutrition.0028_ingredient_dietary_properties... OK
|
||||
Applying nutrition.0029_ingredient_nutriscore... OK
|
||||
Applying manager.0011_remove_set_exercises... OK
|
||||
Applying exercises.0002_auto_20150307_1841... OK
|
||||
Applying exercises.0003_auto_20160921_2000... OK
|
||||
Applying exercises.0004_auto_20170404_0114... OK
|
||||
outbound TCP connections the wger process holds right now: 1 020012AC:CDBA
|
||||
1 060012AC:CC06
|
||||
@@ -0,0 +1,6 @@
|
||||
3.9 browser form, https Origin + CSRF, RIGHT password: ok
|
||||
3.9 browser form, WRONG password: POST /en/user/login -> 200 (refused)
|
||||
3.9/0.7 phone-app route /allauth/app/v1/auth/login, RIGHT password: http=200 keys=['access_token', 'is_authenticated', 'refresh_token', 'session_token']
|
||||
the API with that token: http=200
|
||||
3.9/0.7 phone-app route /allauth/app/v1/auth/login, WRONG password: http=400 keys=['errors', 'status']
|
||||
1.8 unknown page through traefik: http=404 debug-page=False traceback=False
|
||||
@@ -0,0 +1,8 @@
|
||||
wger: POST /api/v2/weightentry/ http=201
|
||||
seed: {'weight': '84.42'}
|
||||
2.8 POST /api/v2/gallery/ (multipart, through traefik): http=201 {"id":1,"date":"2026-10-01","image":"…","description":"felhom pilot","heig
|
||||
2.8 GET the uploaded image back through traefik (/media/gallery/1/def0a0d8-3cc7-4384-96df-ddfcdfaefb8d.png): http=404 bytes=2830 same=False
|
||||
2.8 the same image with NO session: http=404
|
||||
wger: readback of the seeded weight entry http=200 found=True
|
||||
verify (fixture readback, with its negative controls): True
|
||||
2.7 the volumes' size after the seed: wger_wger_data=4.2M wger_wger_media=16K
|
||||
@@ -0,0 +1,3 @@
|
||||
4.4 negative control: docker pause wger at 15:09:17 — controller state before: running
|
||||
states seen while paused (s, (state, health)): [(4, ('stopped', None))]
|
||||
after unpause, state running again after 40 s
|
||||
@@ -0,0 +1,2 @@
|
||||
5.1 wger cgroup from birth: 90 samples over 183 s; limit 402653184
|
||||
peak anon 266.8 MiB (69.5 % of the limit); peak swap 0 bytes; oom_kill max 0; last: 0 running healthy
|
||||
@@ -0,0 +1,91 @@
|
||||
epoch,anon,current,swap,oom_kill,limit,restarts status health
|
||||
1790860027.558,34242560,37122048,0,0,402653184,0 running starting
|
||||
1790860029.614,18444288,25464832,0,0,402653184,0 running starting
|
||||
1790860031.672,90976256,98574336,0,0,402653184,0 running starting
|
||||
1790860033.723,105848832,113618944,0,0,402653184,0 running starting
|
||||
1790860035.782,110673920,118657024,0,0,402653184,0 running starting
|
||||
1790860037.837,111398912,119705600,0,0,402653184,0 running starting
|
||||
1790860039.892,111403008,119754752,0,0,402653184,0 running starting
|
||||
1790860041.949,112967680,121237504,0,0,402653184,0 running starting
|
||||
1790860044.001,115335168,123891712,0,0,402653184,0 running starting
|
||||
1790860046.057,117252096,125751296,0,0,402653184,0 running starting
|
||||
1790860048.107,117264384,126058496,0,0,402653184,0 running starting
|
||||
1790860050.164,119808000,128299008,0,0,402653184,0 running starting
|
||||
1790860052.220,120111104,128561152,0,0,402653184,0 running starting
|
||||
1790860054.275,120111104,128749568,0,0,402653184,0 running starting
|
||||
1790860056.329,120135680,128872448,0,0,402653184,0 running starting
|
||||
1790860058.379,120135680,128794624,0,0,402653184,0 running starting
|
||||
1790860060.436,120135680,128815104,0,0,402653184,0 running starting
|
||||
1790860062.486,120135680,128634880,0,0,402653184,0 running starting
|
||||
1790860064.545,120143872,128770048,0,0,402653184,0 running starting
|
||||
1790860066.596,121110528,129777664,0,0,402653184,0 running starting
|
||||
1790860068.655,121233408,130125824,0,0,402653184,0 running starting
|
||||
1790860070.707,122281984,131215360,0,0,402653184,0 running starting
|
||||
1790860072.769,122286080,131518464,0,0,402653184,0 running starting
|
||||
1790860074.818,28049408,39301120,0,0,402653184,0 running starting
|
||||
1790860076.874,110399488,121991168,0,0,402653184,0 running starting
|
||||
1790860078.933,99303424,110919680,0,0,402653184,0 running starting
|
||||
1790860080.990,109289472,121643008,0,0,402653184,0 running starting
|
||||
1790860083.042,189288448,202596352,0,0,402653184,0 running starting
|
||||
1790860085.086,191352832,204468224,0,0,402653184,0 running starting
|
||||
1790860087.129,192958464,207355904,0,0,402653184,0 running starting
|
||||
1790860089.200,279789568,304762880,0,0,402653184,0 running healthy
|
||||
1790860091.244,197033984,210419712,0,0,402653184,0 running healthy
|
||||
1790860093.290,197033984,210309120,0,0,402653184,0 running healthy
|
||||
1790860095.334,197505024,211095552,0,0,402653184,0 running healthy
|
||||
1790860097.384,197505024,211075072,0,0,402653184,0 running healthy
|
||||
1790860099.438,197505024,210812928,0,0,402653184,0 running healthy
|
||||
1790860101.495,197505024,211075072,0,0,402653184,0 running healthy
|
||||
1790860103.545,197505024,210812928,0,0,402653184,0 running healthy
|
||||
1790860105.590,197505024,211075072,0,0,402653184,0 running healthy
|
||||
1790860107.649,197505024,210812928,0,0,402653184,0 running healthy
|
||||
1790860109.691,197505024,210808832,0,0,402653184,0 running healthy
|
||||
1790860111.735,197505024,211070976,0,0,402653184,0 running healthy
|
||||
1790860113.790,197505024,210808832,0,0,402653184,0 running healthy
|
||||
1790860115.844,197505024,210808832,0,0,402653184,0 running healthy
|
||||
1790860117.893,198356992,212152320,0,0,402653184,0 running healthy
|
||||
1790860119.953,198356992,211935232,0,0,402653184,0 running healthy
|
||||
1790860122.009,198356992,211673088,0,0,402653184,0 running healthy
|
||||
1790860124.075,198356992,211931136,0,0,402653184,0 running healthy
|
||||
1790860126.124,198356992,212152320,0,0,402653184,0 running healthy
|
||||
1790860128.175,198356992,211968000,0,0,402653184,0 running healthy
|
||||
1790860130.231,198356992,211705856,0,0,402653184,0 running healthy
|
||||
1790860132.288,198356992,211980288,0,0,402653184,0 running healthy
|
||||
1790860134.342,198356992,211709952,0,0,402653184,0 running healthy
|
||||
1790860136.398,198356992,211972096,0,0,402653184,0 running healthy
|
||||
1790860138.453,198356992,211709952,0,0,402653184,0 running healthy
|
||||
1790860140.506,198356992,212295680,0,0,402653184,0 running healthy
|
||||
1790860142.560,198356992,211980288,0,0,402653184,0 running healthy
|
||||
1790860144.608,271659008,285794304,0,0,402653184,0 running healthy
|
||||
1790860146.664,198356992,211714048,0,0,402653184,0 running healthy
|
||||
1790860148.718,198365184,211906560,0,0,402653184,0 running healthy
|
||||
1790860150.769,198365184,211722240,0,0,402653184,0 running healthy
|
||||
1790860152.816,198483968,212156416,0,0,402653184,0 running healthy
|
||||
1790860154.870,201388032,215052288,0,0,402653184,0 running healthy
|
||||
1790860156.928,201388032,214929408,0,0,402653184,0 running healthy
|
||||
1790860158.982,201392128,215191552,0,0,402653184,0 running healthy
|
||||
1790860161.025,201392128,215191552,0,0,402653184,0 paused unhealthy
|
||||
1790860163.070,201392128,215191552,0,0,402653184,0 paused unhealthy
|
||||
1790860165.119,201392128,215191552,0,0,402653184,0 paused unhealthy
|
||||
1790860167.178,201392128,215191552,0,0,402653184,0 running unhealthy
|
||||
1790860169.223,201392128,215191552,0,0,402653184,0 running unhealthy
|
||||
1790860171.267,201392128,215191552,0,0,402653184,0 running unhealthy
|
||||
1790860173.325,201392128,215187456,0,0,402653184,0 running unhealthy
|
||||
1790860175.380,201392128,215187456,0,0,402653184,0 running unhealthy
|
||||
1790860177.433,201392128,215187456,0,0,402653184,0 running unhealthy
|
||||
1790860179.477,201392128,214925312,0,0,402653184,0 running unhealthy
|
||||
1790860181.522,201392128,215187456,0,0,402653184,0 running unhealthy
|
||||
1790860183.576,201392128,215187456,0,0,402653184,0 running unhealthy
|
||||
1790860185.623,201359360,215117824,0,0,402653184,0 running unhealthy
|
||||
1790860187.666,201359360,215379968,0,0,402653184,0 running unhealthy
|
||||
1790860189.713,201359360,215117824,0,0,402653184,0 running unhealthy
|
||||
1790860191.758,201359360,215117824,0,0,402653184,0 running unhealthy
|
||||
1790860193.802,201359360,215117824,0,0,402653184,0 running unhealthy
|
||||
1790860195.849,201359360,215379968,0,0,402653184,0 running unhealthy
|
||||
1790860197.894,201752576,215539712,0,0,402653184,0 running healthy
|
||||
1790860199.943,201752576,215515136,0,0,402653184,0 running healthy
|
||||
1790860201.993,201752576,215515136,0,0,402653184,0 running healthy
|
||||
1790860204.044,201752576,215252992,0,0,402653184,0 running healthy
|
||||
1790860206.096,201752576,215515136,0,0,402653184,0 running healthy
|
||||
1790860208.144,201752576,215515136,0,0,402653184,0 running healthy
|
||||
1790860210.194,201752576,215515136,0,0,402653184,0 running healthy
|
||||
|
@@ -0,0 +1,4 @@
|
||||
2.6 stop -> 200
|
||||
2.6 remove, KEEP drive data (wger has no drive data) -> 200 {'ok': True, 'data': {'removed': 'wger', 'volumes_removed': ['wger_wger_data', 'wger_wger_media'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/sys_drive/felhom-d
|
||||
left after: stack dir / containers / volumes: /opt/docker/stacks/wger
|
||||
the image (kept per decision 53 until the sweep):
|
||||
@@ -0,0 +1,55 @@
|
||||
##### wger second walk — controller gitea.dooplex.hu/admin/felhom-controller:0.285.0 catalog e9f50b5 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
|
||||
deploy -> True
|
||||
household admin signs in (the admin exists, the setup is done): ok
|
||||
|
||||
== 3.4 a STRANGER, after the setup — no dashboard session, no gate cookie, through traefik
|
||||
wger source: where registration and guests are decided:
|
||||
/home/wger/src/wger/utils/context_processor.py:36: 'allow_registration': settings.WGER_SETTINGS.get('ALLOW_REGISTRATION', False),
|
||||
/home/wger/src/wger/utils/middleware.py:63: settings.WGER_SETTINGS['ALLOW_GUEST_USERS']
|
||||
/home/wger/src/wger/core/views/misc.py:55: if not settings.WGER_SETTINGS['ALLOW_GUEST_USERS']:
|
||||
/home/wger/src/wger/core/views/user.py:291: if not settings.WGER_SETTINGS['ALLOW_REGISTRATION']:
|
||||
/home/wger/src/wger/core/account_adapter.py:36: return settings.WGER_SETTINGS['ALLOW_REGISTRATION']
|
||||
/home/wger/src/wger/software/views.py:74: context['allow_registration'] = settings.WGER_SETTINGS['ALLOW_REGISTRATION']
|
||||
/home/wger/src/wger/software/views.py:75: context['allow_guest_users'] = settings.WGER_SETTINGS['ALLOW_GUEST_USERS']
|
||||
GET /en/user/registration -> 200 form fields: ['csrfmiddlewaretoken', 'email', 'language', 'next', 'password1', 'password2', 'username', 'viewport']
|
||||
POST /en/user/registration as stranger272284 -> 302; form errors: []
|
||||
the stranger then signs in with that account -> 302 (302 = IN)
|
||||
and reads the API as that user -> 200
|
||||
the app's own user list now (admin's view, count only): 2 ['admin', 'stranger']
|
||||
|
||||
-- guests: wger's own 'try as guest' path, as a stranger
|
||||
/home/wger/src/wger/core/views/user.py:283: closed" page), and temporary (guest) users may still reach the
|
||||
/home/wger/src/wger/core/views/user.py:787: allauth's login view, with one wger carve-out: temporary (guest) users are
|
||||
/home/wger/src/wger/utils/context_processor.py:55: # Flag for guest users
|
||||
/home/wger/src/wger/utils/middleware.py:29:from wger.core.demo import create_temporary_user
|
||||
/home/wger/src/wger/utils/middleware.py:43: # Don't create guest users for requests that are accessing the site
|
||||
/home/wger/src/wger/utils/middleware.py:68: logger.debug('creating a new guest user now')
|
||||
/home/wger/src/wger/utils/middleware.py:69: user = create_temporary_user(request)
|
||||
GET /en/user/demo-entries as a stranger -> 500
|
||||
GET /en/dashboard as a stranger -> 200
|
||||
users after the guest tries: 4 ['2167c0bc', '792dae77', 'admin', 'stranger']
|
||||
|
||||
== 2.8 the uploaded photo: on the volume? who serves /media/?
|
||||
POST /api/v2/gallery/ -> 201 image path: /media/gallery/1/e91d6ece-866d-44c7-9e3f-1a6db6a6107b.png
|
||||
the file on the media volume: -rw-r--r-- 1 wger wger 70 Oct 1 15:13 /home/wger/media/gallery/1/e91d6ece-866d-44c7-9e3f-1a6db6a6107b.png
|
||||
GET it through traefik, signed in -> 404
|
||||
inside the container, straight at runserver (no traefik): HTTP Error 404: Not Found
|
||||
wger's urls.py on /media and /static: 350: 'api/v2/min-server-version/',
|
||||
352: name='min_server_version',
|
||||
393:# URL for user uploaded files, served like this during development only
|
||||
396: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
whitenoise / static middleware in settings: /home/wger/src/settings/ci.py:117:MEDIA_ROOT = '/tmp/'
|
||||
/home/wger/src/settings/main.py:142:MEDIA_ROOT = env.str('DJANGO_MEDIA_ROOT', '/home/wger/media')
|
||||
/home/wger/src/settings/main.py:143:STATIC_ROOT = env.str('DJANGO_STATIC_ROOT', '/home/wger/static')
|
||||
/home/wger/src/settings/settings_global.py:48:MEDIA_ROOT = BASE_DIR.parent / 'media'
|
||||
/home/wger/src/settings/settings_global.py:49:STATIC_ROOT = BASE_DIR.parent / 'static'
|
||||
upstream's own production compose serves /media with nginx — the image's docs: celery
|
||||
Dockerfile
|
||||
entrypoint.sh
|
||||
README.md
|
||||
|
||||
== 1.7 static files (collectstatic needs DJANGO_DEBUG == "False"; the template sets none)
|
||||
static refs on the login page: ['/static/css/workout-manager.css', '/static/bootstrap-compiled.css', '/static/css/bootstrap-custom.css']
|
||||
GET /static/css/workout-manager.css -> 404 (2830 chars)
|
||||
GET /static/bootstrap-compiled.css -> 404 (2830 chars)
|
||||
static root inside the container: 4.0K /home/wger/static
|
||||
@@ -0,0 +1,2 @@
|
||||
remove WITH data -> 200 {'ok': True, 'data': {'removed': 'wger', 'volumes_removed': ['wger_wger_data', 'wger_wger_media'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját adato
|
||||
left after: stack dir / containers / volumes: /opt/docker/stacks/wger
|
||||
@@ -0,0 +1,26 @@
|
||||
git:
|
||||
branch: main
|
||||
repo_url: https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
sync_interval: 15m
|
||||
token: <redacted>
|
||||
username: ""
|
||||
hub:
|
||||
|
||||
CONTROL 1 — the box's catalog clone: 6d72c09 Merge branch 'main' of https://gitea.dooplex.hu/admin/app-catalog-felhom.eu
|
||||
https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
CONTROL 2 — the LIVE catalog main (unchanged by this act): ['6d72c091e039197758bf999ef8154b13530482b1']
|
||||
CONTROL 3 — expected source: live
|
||||
leftovers named wger: /opt/docker/stacks/wger
|
||||
6d72c09 Merge branch 'main' of https://gitea.dooplex.hu/admin/app-catalog-felhom.eu
|
||||
https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git
|
||||
filebrowser Up 7 hours (healthy)
|
||||
felhom-controller Up 32 seconds (healthy)
|
||||
paperless-webserver Up 13 hours (healthy)
|
||||
paperless-redis Up 13 hours (healthy)
|
||||
paperless-postgres Up 13 hours (healthy)
|
||||
traefik Up 6 days
|
||||
/root/wger-mem.csv
|
||||
/root/wger-poll.txt
|
||||
0
|
||||
0
|
||||
actualbudget adventurelog audiobookshelf bentopdf bookstack calcom calibre-web chaoscrash chaosoom chaosoomb claper code-server crafty-controller docmost emby filebrowser ghost gitea glance gokapi grafana gramps-web home-assistant homebox homepage immich jellyfin kimai komga mealie
|
||||
@@ -0,0 +1,89 @@
|
||||
##### wger on 9202 — controller gitea.dooplex.hu/admin/felhom-controller:0.285.0
|
||||
catalog clone: e9f50b5 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
|
||||
before: stack/volumes named wger: /opt/docker/stacks/wger
|
||||
guest swap (free -m): Swap: 512 19 492
|
||||
sampler started
|
||||
poller started
|
||||
deploy press at 15:06:36
|
||||
15:06:36 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD']
|
||||
15:06:37 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
15:08:12 [1] deployed, controller state=running, pinned={'wger': 'wger/server:2.7'}
|
||||
deploy -> True
|
||||
controller log (after_install / hold): 2026/10/01 13:08:09 install_hold.go:135: [INFO] [stacks] wger: install hold OPENED by after_install — the app is reached as without a hold
|
||||
4.3 press -> docker healthy: 107 s; RestartCount: 0; start_period in compose: start_period: 30s
|
||||
controller state: running
|
||||
3.5 stranger polls of admin/adminadmin from the press: 92 tries; codes {'404': 90, '401': 1, '400': 1}
|
||||
first 3: ['13:06:35 404 404 page not found ', '13:06:36 404 404 page not found ', '13:06:37 404 404 page not found ']
|
||||
last 3: ['13:08:07 404 404 page not found ', '13:08:08 401 {"error":"this app is waiting for its first setup"}', '13:08:10 400 {"status": 400, "errors": [{"message": "The username and/or password you specified are not']
|
||||
any 200 with a token: False
|
||||
1.5 the server process: python3 manage.py runserver 0.0.0.0:8000 | /usr/bin/python3 manage.py runserver 0.0.0.0:8000
|
||||
1.7 image entrypoint:
|
||||
1.7 entrypoint switches (if-tests in /home/wger/entrypoint.sh):
|
||||
10:if [ "$YARN_PROCESS_STATIC" == "True" ];
|
||||
21:if [ "$DJANGO_CLEAR_STATIC_FIRST" == "False" ]; then
|
||||
27:if [[ "$DJANGO_DEBUG" == "False" && "${DJANGO_COLLECTSTATIC_ON_STARTUP:-True}" == "True" ]];
|
||||
34:if [[ "$DJANGO_PERFORM_MIGRATIONS" == "True" ]];
|
||||
41:if [[ "$SYNC_EXERCISES_ON_STARTUP" == "True" ]];
|
||||
48:if [[ "$DOWNLOAD_EXERCISE_IMAGES_ON_STARTUP" == "True" ]];
|
||||
55:if [[ "$DOWNLOAD_EXERCISE_VIDEOS_ON_STARTUP" == "True" ]];
|
||||
62:if [[ "$LOAD_ONLINE_FIXTURES_ON_STARTUP" == "True" ]];
|
||||
69:if [[ "$SYNC_INGREDIENTS_ON_STARTUP" == "True" ]];
|
||||
81:if [[ "$WGER_USE_GUNICORN" == "True" ]];
|
||||
0.4/1.7 the IMAGE's env defaults for those switches (+ debug/mail/sync):
|
||||
the RUNNING container's env, the same switches (values: only these named, non-secret ones):
|
||||
container: SITE_URL=https://fitness.enkisfelhom.hu
|
||||
container: AXES_COOLOFF_TIME=5
|
||||
container: DJANGO_PERFORM_MIGRATIONS=True
|
||||
container: AXES_HANDLER=axes.handlers.database.AxesDatabaseHandler
|
||||
container: AXES_LOCKOUT_PARAMETERS=username
|
||||
container: X_FORWARDED_PROTO_HEADER_SET=True
|
||||
container: TZ=Europe/Budapest
|
||||
container: CSRF_TRUSTED_ORIGINS=https://fitness.enkisfelhom.hu
|
||||
container env NAMES (all): APP_BUILD_COMMIT APP_BUILD_DATE AXES_COOLOFF_TIME AXES_HANDLER AXES_LOCKOUT_PARAMETERS CSRF_TRUSTED_ORIGINS DEBIAN_FRONTEND DJANGO_DB_DATABASE DJANGO_DB_ENGINE DJANGO_DB_HOST DJANGO_DB_PASSWORD DJANGO_DB_PORT DJANGO_DB_USER DJANGO_PERFORM_MIGRATIONS DJANGO_SETTINGS_MODULE LANG LANGUAGE LC_ALL PATH PYTHONDONTWRITEBYTECODE PYTHONPATH PYTHONUNBUFFERED SECRET_KEY SITE_URL TZ X_FORWARDED_PROTO_HEADER_SET
|
||||
1.6 the env names wger's settings READ (settings/main.py, env.*('NAME'...)):
|
||||
ACCESS_TOKEN_LIFETIME ALLOW_GUEST_USERS ALLOW_REGISTRATION ALLOW_UPLOAD_VIDEOS AUTH_PROXY_CREATE_UNKNOWN_USER AUTH_PROXY_HEADER AUTH_PROXY_TRUSTED_IPS AUTH_PROXY_USER_EMAIL_HEADER AUTH_PROXY_USER_NAME_HEADER AWS_ACCESS_KEY_ID AWS_S3_DOMAIN AWS_S3_REGION_NAME AWS_SECRET_ACCESS_KEY AWS_STORAGE_BUCKET_NAME AXES_COOLOFF_TIME AXES_ENABLED AXES_FAILURE_LIMIT AXES_HANDLER AXES_IPWARE_PROXY_COUNT AXES_LOCKOUT_PARAMETERS CACHE_API_EXERCISES_CELERY CELERY_BACKEND CELERY_BROKER DJANGO_ADMINS DJANGO_CACHE_BACKEND DJANGO_CACHE_CLIENT_CLASS DJANGO_CACHE_CLIENT_PASSWORD DJANGO_CACHE_CLIENT_SSL_CERTFILE DJANGO_CACHE_CLIENT_SSL_CERT_REQS DJANGO_CACHE_CLIENT_SSL_KEYFILE DJANGO_CACHE_LOCATION DJANGO_CACHE_TIMEOUT DJANGO_DB_DATABASE DJANGO_DB_ENGINE DJANGO_DB_HOST DJANGO_DB_PASSWORD DJANGO_DB_PORT DJANGO_DB_USER DJANGO_MEDIA_ROOT DJANGO_STATIC_ROOT DOWNLOAD_INGREDIENTS_FROM EMAIL_HOST EMAIL_HOST_PASSWORD EMAIL_HOST_USER EMAIL_PORT EMAIL_USE_SSL EMAIL_USE_TLS ENABLE_EMAIL EXERCISE_CACHE_TTL EXPORT_INGREDIENTS_BULK_CELERY EXPOSE_PROMETHEUS_METRICS FROM_EMAIL IDP_OIDC_PRIVATE_KEY JWT_PRIVATE_KEY JWT_PUBLIC_KEY LOGIN_REDIRECT_URL LOG_LEVEL_PYTHON MEDIA_URL MIN_ACCOUNT_AGE_TO_TRUST NUMBER_OF_PROXIES POWERSYNC_TOKEN_LIFETIME POWERSYNC_URL POWERSYNC_URL_PATH RECAPTCHA_PRIVATE_KEY RECAPTCHA_PUBLIC_KEY RECAPTCHA_REQUIRED_SCORE REFRESH_TOKEN_LIFETIME S3_MEDIA_FILES_LOCATION S3_STATIC_FILES_LOCATION SECRET_KEY SECURE_PROXY_SSL_HEADER SITE_URL STATIC_URL SYNC_EXERCISE_IMAGES_CELERY SYNC_EXERCISES_CELERY SYNC_EXERCISE_VIDEOS_CELERY SYNC_INGREDIENTS_CELERY SYNC_OFF_DAILY_DELTA_CELERY TIME_ZONE USE_CELERY USE_RECAPTCHA USE_S3_MEDIA_FILES USE_S3_STATIC_FILES USE_S3_URL_FOR_MEDIA USE_S3_URL_FOR_STATIC USE_X_FORWARDED_HOST WGER_MAX_SESSION_LENGTH_HOURS WGER_SHOW_APP_STORE_LINKS WGER_SOCIAL_PROVIDERS X_FORWARDED_PROTO_HEADER_SET
|
||||
of which key/secret-like: ['ACCESS_TOKEN_LIFETIME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'DJANGO_CACHE_CLIENT_PASSWORD', 'DJANGO_CACHE_CLIENT_SSL_KEYFILE', 'DJANGO_DB_PASSWORD', 'EMAIL_HOST_PASSWORD', 'IDP_OIDC_PRIVATE_KEY', 'JWT_PRIVATE_KEY', 'JWT_PUBLIC_KEY', 'POWERSYNC_TOKEN_LIFETIME', 'RECAPTCHA_PRIVATE_KEY', 'RECAPTCHA_PUBLIC_KEY', 'REFRESH_TOKEN_LIFETIME', 'SECRET_KEY']
|
||||
set in the container: ['DJANGO_DB_PASSWORD', 'SECRET_KEY'] NOT set: ['ACCESS_TOKEN_LIFETIME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'DJANGO_CACHE_CLIENT_PASSWORD', 'DJANGO_CACHE_CLIENT_SSL_KEYFILE', 'EMAIL_HOST_PASSWORD', 'IDP_OIDC_PRIVATE_KEY', 'JWT_PRIVATE_KEY', 'JWT_PUBLIC_KEY', 'POWERSYNC_TOKEN_LIFETIME', 'RECAPTCHA_PRIVATE_KEY', 'RECAPTCHA_PUBLIC_KEY', 'REFRESH_TOKEN_LIFETIME']
|
||||
1.8/0.4 Django settings inside the app:
|
||||
DEBUG False
|
||||
EMAIL_BACKEND django.core.mail.backends.console.EmailBackend
|
||||
WGER_SETTINGS sync/download: {'ALLOW_GUEST_USERS': True, 'ALLOW_REGISTRATION': True, 'ALLOW_UPLOAD_VIDEOS': True, 'EMAIL_FROM': 'wger Workout Manager <wger@example.com>', 'DOWNLOAD_INGREDIENTS_FROM': 'WGER', 'SYNC_EXERCISES_CELERY': False, 'SYNC_EXERCISE_IMAGES_CELERY': False, 'SYNC_EXERCISE_VIDEOS_CELERY': False, 'SYNC_INGREDIENTS_CELERY': False, 'SYNC_OFF_DAILY_DELTA_CELERY': False, 'SYNC_INGREDIENTS_DUMP_URL': 'https://wger.de/media/ingredients/ingredients.jsonl.gz', 'TWITTER': False, 'MASTODON': 'https://fosstodon.org/@wger', 'USE_CELERY': False}
|
||||
JWT key loaded: False (env in this shell is not the server env)
|
||||
0.4/0.5 the container's first-start log lines about the network (sync/download/http):
|
||||
Apply all migrations: account, actstream, allauth_idp_oidc, auth, authtoken, axes, config, contenttypes, core, easy_thumbnails, exercises, gallery, gym, mailer, manager, measurements, mfa, nutrition, sessions, sites, socialaccount, token_blacklist, trophies, weight
|
||||
Applying nutrition.0012_alter_ingredient_license_author... OK
|
||||
Applying exercises.0001_initial... OK
|
||||
Applying nutrition.0013_ingredient_image... OK
|
||||
Applying nutrition.0015_alter_ingredient_creation_date_and_more... OK
|
||||
Applying nutrition.0024_remove_ingredient_status... OK
|
||||
Applying nutrition.0028_ingredient_dietary_properties... OK
|
||||
Applying nutrition.0029_ingredient_nutriscore... OK
|
||||
Applying manager.0011_remove_set_exercises... OK
|
||||
Applying exercises.0002_auto_20150307_1841... OK
|
||||
Applying exercises.0003_auto_20160921_2000... OK
|
||||
Applying exercises.0004_auto_20170404_0114... OK
|
||||
outbound TCP connections the wger process holds right now: 1 020012AC:CDBA
|
||||
1 060012AC:CC06
|
||||
3.9 browser form, https Origin + CSRF, RIGHT password: ok
|
||||
3.9 browser form, WRONG password: POST /en/user/login -> 200 (refused)
|
||||
3.9/0.7 phone-app route /allauth/app/v1/auth/login, RIGHT password: http=200 keys=['access_token', 'is_authenticated', 'refresh_token', 'session_token']
|
||||
the API with that token: http=200
|
||||
3.9/0.7 phone-app route /allauth/app/v1/auth/login, WRONG password: http=400 keys=['errors', 'status']
|
||||
1.8 unknown page through traefik: http=404 debug-page=False traceback=False
|
||||
wger: POST /api/v2/weightentry/ http=201
|
||||
seed: {'weight': '84.42'}
|
||||
2.8 POST /api/v2/gallery/ (multipart, through traefik): http=201 {"id":1,"date":"2026-10-01","image":"…","description":"felhom pilot","heig
|
||||
2.8 GET the uploaded image back through traefik (/media/gallery/1/def0a0d8-3cc7-4384-96df-ddfcdfaefb8d.png): http=404 bytes=2830 same=False
|
||||
2.8 the same image with NO session: http=404
|
||||
wger: readback of the seeded weight entry http=200 found=True
|
||||
verify (fixture readback, with its negative controls): True
|
||||
2.7 the volumes' size after the seed: wger_wger_data=4.2M wger_wger_media=16K
|
||||
4.4 negative control: docker pause wger at 15:09:17 — controller state before: running
|
||||
states seen while paused (s, (state, health)): [(4, ('stopped', None))]
|
||||
after unpause, state running again after 40 s
|
||||
5.1 wger cgroup from birth: 90 samples over 183 s; limit 402653184
|
||||
peak anon 266.8 MiB (69.5 % of the limit); peak swap 0 bytes; oom_kill max 0; last: 0 running healthy
|
||||
2.6 stop -> 200
|
||||
2.6 remove, KEEP drive data (wger has no drive data) -> 200 {'ok': True, 'data': {'removed': 'wger', 'volumes_removed': ['wger_wger_data', 'wger_wger_media'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'backup_paths_removed': ['/mnt/sys_drive/felhom-d
|
||||
left after: stack dir / containers / volumes: /opt/docker/stacks/wger
|
||||
the image (kept per decision 53 until the sweep):
|
||||
@@ -0,0 +1,60 @@
|
||||
##### wger second walk — controller gitea.dooplex.hu/admin/felhom-controller:0.285.0 catalog e9f50b5 calibre-web: the admin login name is generated at install (09 decision 61, R-752)
|
||||
15:11:29 [1] required fields filled beyond DOMAIN/SUBDOMAIN: ['ADMIN_PASSWORD']
|
||||
15:11:29 [1] deploy -> 202 {'ok': True, 'message': 'Telepítés elindítva – az állapot a kártyán követhető'}
|
||||
15:13:00 [1] deployed, controller state=running, pinned={'wger': 'wger/server:2.7'}
|
||||
deploy -> True
|
||||
household admin signs in (the admin exists, the setup is done): ok
|
||||
|
||||
== 3.4 a STRANGER, after the setup — no dashboard session, no gate cookie, through traefik
|
||||
wger source: where registration and guests are decided:
|
||||
/home/wger/src/wger/utils/context_processor.py:36: 'allow_registration': settings.WGER_SETTINGS.get('ALLOW_REGISTRATION', False),
|
||||
/home/wger/src/wger/utils/middleware.py:63: settings.WGER_SETTINGS['ALLOW_GUEST_USERS']
|
||||
/home/wger/src/wger/core/views/misc.py:55: if not settings.WGER_SETTINGS['ALLOW_GUEST_USERS']:
|
||||
/home/wger/src/wger/core/views/user.py:291: if not settings.WGER_SETTINGS['ALLOW_REGISTRATION']:
|
||||
/home/wger/src/wger/core/account_adapter.py:36: return settings.WGER_SETTINGS['ALLOW_REGISTRATION']
|
||||
/home/wger/src/wger/software/views.py:74: context['allow_registration'] = settings.WGER_SETTINGS['ALLOW_REGISTRATION']
|
||||
/home/wger/src/wger/software/views.py:75: context['allow_guest_users'] = settings.WGER_SETTINGS['ALLOW_GUEST_USERS']
|
||||
GET /en/user/registration -> 200 form fields: ['csrfmiddlewaretoken', 'email', 'language', 'next', 'password1', 'password2', 'username', 'viewport']
|
||||
POST /en/user/registration as stranger272284 -> 302; form errors: []
|
||||
the stranger then signs in with that account -> 302 (302 = IN)
|
||||
and reads the API as that user -> 200
|
||||
the app's own user list now (admin's view, count only): 2 ['admin', 'stranger']
|
||||
|
||||
-- guests: wger's own 'try as guest' path, as a stranger
|
||||
/home/wger/src/wger/core/views/user.py:283: closed" page), and temporary (guest) users may still reach the
|
||||
/home/wger/src/wger/core/views/user.py:787: allauth's login view, with one wger carve-out: temporary (guest) users are
|
||||
/home/wger/src/wger/utils/context_processor.py:55: # Flag for guest users
|
||||
/home/wger/src/wger/utils/middleware.py:29:from wger.core.demo import create_temporary_user
|
||||
/home/wger/src/wger/utils/middleware.py:43: # Don't create guest users for requests that are accessing the site
|
||||
/home/wger/src/wger/utils/middleware.py:68: logger.debug('creating a new guest user now')
|
||||
/home/wger/src/wger/utils/middleware.py:69: user = create_temporary_user(request)
|
||||
GET /en/user/demo-entries as a stranger -> 500
|
||||
GET /en/dashboard as a stranger -> 200
|
||||
users after the guest tries: 4 ['2167c0bc', '792dae77', 'admin', 'stranger']
|
||||
|
||||
== 2.8 the uploaded photo: on the volume? who serves /media/?
|
||||
POST /api/v2/gallery/ -> 201 image path: /media/gallery/1/e91d6ece-866d-44c7-9e3f-1a6db6a6107b.png
|
||||
the file on the media volume: -rw-r--r-- 1 wger wger 70 Oct 1 15:13 /home/wger/media/gallery/1/e91d6ece-866d-44c7-9e3f-1a6db6a6107b.png
|
||||
GET it through traefik, signed in -> 404
|
||||
inside the container, straight at runserver (no traefik): HTTP Error 404: Not Found
|
||||
wger's urls.py on /media and /static: 350: 'api/v2/min-server-version/',
|
||||
352: name='min_server_version',
|
||||
393:# URL for user uploaded files, served like this during development only
|
||||
396: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
whitenoise / static middleware in settings: /home/wger/src/settings/ci.py:117:MEDIA_ROOT = '/tmp/'
|
||||
/home/wger/src/settings/main.py:142:MEDIA_ROOT = env.str('DJANGO_MEDIA_ROOT', '/home/wger/media')
|
||||
/home/wger/src/settings/main.py:143:STATIC_ROOT = env.str('DJANGO_STATIC_ROOT', '/home/wger/static')
|
||||
/home/wger/src/settings/settings_global.py:48:MEDIA_ROOT = BASE_DIR.parent / 'media'
|
||||
/home/wger/src/settings/settings_global.py:49:STATIC_ROOT = BASE_DIR.parent / 'static'
|
||||
upstream's own production compose serves /media with nginx — the image's docs: celery
|
||||
Dockerfile
|
||||
entrypoint.sh
|
||||
README.md
|
||||
|
||||
== 1.7 static files (collectstatic needs DJANGO_DEBUG == "False"; the template sets none)
|
||||
static refs on the login page: ['/static/css/workout-manager.css', '/static/bootstrap-compiled.css', '/static/css/bootstrap-custom.css']
|
||||
GET /static/css/workout-manager.css -> 404 (2830 chars)
|
||||
GET /static/bootstrap-compiled.css -> 404 (2830 chars)
|
||||
static root inside the container: 4.0K /home/wger/static
|
||||
remove WITH data -> 200 {'ok': True, 'data': {'removed': 'wger', 'volumes_removed': ['wger_wger_data', 'wger_wger_media'], 'hdd_paths_removed': [], 'hdd_paths_preserved': [], 'hdd_note': 'Az alkalmazás nem tárolt saját adato
|
||||
left after: stack dir / containers / volumes: /opt/docker/stacks/wger
|
||||
@@ -0,0 +1,80 @@
|
||||
# The new-app checklist — review, gate, wger pilot, gap page (2026-10-01)
|
||||
|
||||
Operator request 2026-10-01: a checklist every new app passes before it reaches the live catalog. Reviewer's draft the
|
||||
same day (the brief's appendix; pushed by the operator as `app-catalog-felhom.eu/NEW-APP-CHECKLIST.md`, `6f18f74`).
|
||||
Architecture read: `documentation/architecture/09-update-architecture.md` §3 decisions 13, 22, 37, 42, 45–50, 61;
|
||||
§6.5 (drill catalog). Baselines: catalog `main` `6d72c09` (the draft's merge on top of `9c5eae9`), 53 templates;
|
||||
felhom.eu `b63654a`; register 392 rows, highest R-757; controller on 9202 `0.285.0`.
|
||||
|
||||
| part | done? | what |
|
||||
|---|---|---|
|
||||
| A — the checklist | done, changed | 60 rows in 10 groups (draft: 53 in 10). 7 rows added, 16 "hows" sharpened, 9 citations fixed. `app-catalog-felhom.eu/NEW-APP-CHECKLIST.md`; template `onboarding/_TEMPLATE.md`; pointers in CLAUDE.md, REUSE.md §5, README "Adding a New App" |
|
||||
| B — the gate | done | `scripts/check-onboarding.py`, gate `onboarding` in `catalog_gates.py --fast` (hook + CI); 16 decoy cases, 5 mutants of the gate each turn the suite red (`A/`) |
|
||||
| C — the wger pilot | done | the four problems: 2 caught by the draft as written, 2 missed by the draft's "how" and caught by sharpened/new rows. The pilot ALSO found three live wger defects (R-762, R-763, R-764) |
|
||||
| D — the gap page | done | `onboarding/EXISTING-APPS-GAPS.md` from `scripts/onboarding_gaps.py`, read only |
|
||||
|
||||
## 1. Claims in the draft that were wrong
|
||||
|
||||
| row | the draft said | what is true |
|
||||
|---|---|---|
|
||||
| 3.3 | "32 apps" gated | 33 templates carry `setup_gate: true` today (FIRST-ADMIN's 32 was 2026-09-29) |
|
||||
| 5.2 | "romm OOM at +76 s (decision 22)" | no "+76 s" exists in R-635, decision 22 or any audit; romm's OOM loop is R-635 (six hours after an update called success). Citation replaced |
|
||||
| 5.3 | "gate / review" | **no gate checks it**, and 8 templates differ today (R-758). immich's figure was right: `mem_limit` 4096M vs a 4224M sum, header naming a 256M DB that ran at 512M |
|
||||
| 6.2 | "35 of 53 update at night" | not reproducible from files; 38 of 53 carry a ladder today, and a ladder is not "updates at night" (marks can hold a step for a person) |
|
||||
| 8.3 | (implicit) the asset URL | the canonical template comment says `-logo.webp`; the controller loads `-logo.svg`/`.png` (R-761) |
|
||||
| 1.6 | how = "compose + a login on 9202" | **cannot show R-737**: the web login worked; only the phone app's route failed. Sharpened (list the env the settings READ; log in on every route) + new row 3.9 |
|
||||
| 1.4 | how = "an update on 9202" | a new app pinned at its newest tag has no update to make. Sharpened: install the PREVIOUS release, step INTO the pin |
|
||||
| 0.4 | how = "one packet capture or log read" | no packet-capture tool is in our kit; replaced by the entrypoint read (1.7) + the first-start log + the held connections |
|
||||
| 2.6 | why = "R-756 (refused with a folder present)" | R-756's cause is not known (possibly a 9202 venue artefact); R-442 (the inert "remove with data") added as the measured reason |
|
||||
| — duplicates of gates | 1.1, 2.1, 3.3 (probe flip), 4.2, 6.2, 8.1, 9.4 | each now names its gate: `image-pins`/`image-resolvable`, `volume-persistence`, `probe-measured`, `probe-matches-compose`, `test-record`(+`-move`), `copy-i18n`, `onboarding` |
|
||||
|
||||
**Rows added:** 0.9 (no self-call at the public name, R-739), 1.7 (every entrypoint switch read and decided), 1.8
|
||||
(debug off, R-482), 1.9 (`data_key`), 2.8 (an upload opens again through the front door, R-483), 3.9 (sign in as each
|
||||
client does — browser CSRF + the phone app's route, R-712/R-737), 8.5 (website app count).
|
||||
**Rows sharpened (how or why):** 0.4, 0.7, 1.4, 1.6, 2.3 (R-537/538), 2.6 (R-442), 3.1 (R-612), 3.2 (R-702), 3.4
|
||||
(R-512), 3.6 (a second member), 3.8 (R-713), 4.1 (inspect the image), 4.3 (R-473, R-676), 4.4 (R-613), 4.5 (R-630),
|
||||
5.1 (cgroup, not Docker's OOMKilled — R-528; R-703), 5.2 (R-635, R-514), 6.1 (R-624, R-738's product gap), 6.2/6.3
|
||||
(R-742), 6.5 (R-743), 8.2 (R-515, R-498), 8.3 (R-761).
|
||||
|
||||
## 2. The pilot — would the checklist have caught this week's four wger problems?
|
||||
|
||||
The record against the template as it was on 2026-09-29 (`d0e7e2e`): `wger-as-of-2026-09-29.md` — 27 of 60 rows open,
|
||||
10 of them FAILS. The record as it is now: `app-catalog-felhom.eu/onboarding/wger.md` — 11 open, each a register row.
|
||||
|
||||
| problem | the row that catches it | would the DRAFT's "how" have shown it? | after the review |
|
||||
|---|---|---|---|
|
||||
| **R-737** JWT key missing — the phone app's login 500 | 1.6, 3.9, 0.7 | **No.** "compose + a login on 9202" — the web login worked; only `/allauth/app/v1/auth/login` failed. 0.7 needed a real phone client | 1.6 lists the env the settings READ (`JWT_PRIVATE_KEY` is among 15 key-like names, C2); 3.9 calls the phone route with `curl` |
|
||||
| **R-738** no migration on update | 1.4 (+ 6.1) | **Only if an update was run.** On 2026-09-29 one existed (2.7 since 09-03) and the fixture's read-back caught it on 09-30; for a NEW app at its newest tag, the draft's how has nothing to run | 1.4: step from the previous release INTO the pin; 1.7: the entrypoint read names `DJANGO_PERFORM_MIGRATIONS` statically |
|
||||
| **R-752** a stranger locks everyone out | 3.6 | **Yes** — "N wrong passwords … who is locked" is exactly R-752's measured control | 3.6 says how to tell "everyone": the household's AND a second member's right password, and cites R-753 |
|
||||
| **R-755** development server | 1.5 (+ 1.7) | **Yes** — `ps` in the container shows `manage.py runserver` | 1.7 also finds it statically (`WGER_USE_GUNICORN`) |
|
||||
|
||||
**Three more found by the new and sharpened rows on the current template (C8):** row 2.8 — a photo uploads (201)
|
||||
and never opens (404); row 1.7 — `DJANGO_DEBUG` unset, so `collectstatic` never runs and every CSS/JS file is 404
|
||||
(R-762); row 3.4 — a stranger signs up after the setup and each anonymous visit makes a guest account (R-763);
|
||||
row 7.1 — mail goes to the console (R-764). None of these is in the draft's four; all were on the live template since
|
||||
it was written.
|
||||
|
||||
## 3. The gap page's headline (`onboarding/EXISTING-APPS-GAPS.md`, of 53)
|
||||
|
||||
0 Fit 52 · 1 Images/DB 53 · 2 Storage 38 · 3 Accounts 39 · 4 Health 49 · 5 Resources 24 · 6 Updates 26 · 7 Mail —
|
||||
(6 mapped) · 8 Text 52. Read from files only; "covered" means a file shows the signal named on the page, not that it was
|
||||
re-tested. What NO old app has recorded: the entrypoint switches, the production server, the secrets read (1.4–1.9), a
|
||||
restore round trip, a negative health control, lock-out (except the R-752 apps), a from-birth memory watch (except
|
||||
immich).
|
||||
|
||||
## 4. The live work, and teardown
|
||||
|
||||
9202 only, drill catalog (`app-catalog-drill` `e9f50b5`, wger identical to live). C0 repoint (saved
|
||||
`controller.yaml.pre-checklist1001`; control: the box's clone = drill `e9f50b5`, live `main` unchanged `6d72c09`);
|
||||
C1–C7 walk 1 (install, reads, logins, seed + photo, pause, memory, remove keeping data); C8/C8b walk 2 (stranger
|
||||
sign-up, guests, photo cause, static files, remove with data); C9 restore (clone = live `6d72c09`, standing apps
|
||||
healthy). **Teardown — machine:** wger removed twice through the product, both volumes gone, image deleted by the
|
||||
remove (decision 53), sampler/poller files removed from `/root`; the stack directory remains (the sync creates one per
|
||||
template). **Host:** nothing left (temp scripts removed per call). **Hub:** untouched (9202 is unenrolled).
|
||||
Secrets: the dashboard password and wger's generated password lived in a 0600 scratch directory, never printed;
|
||||
evidence scanned for both values and for token shapes — none.
|
||||
|
||||
## Files
|
||||
|
||||
`A/` decoys (green run; red-proof by mutants) · `B/` upstream reads, the 2026-09-29 template copy, gates then and now ·
|
||||
`C/` the 9202 walks · `tools/` `c_wger.py`, `c_wger2.py` · `wger-as-of-2026-09-29.md` the first-pass record.
|
||||
@@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Part C, the pilot: measure on scratch guest 9202 what wger's onboarding record ("as it is now") has no evidence for.
|
||||
|
||||
argv[1] = drill | walk | restore
|
||||
drill — point 9202 at the drill catalog (`09` §6.5; R-615: the cache dir goes too), save the config first
|
||||
walk — one fresh wger install from the drill catalog and the reads below; then remove it through the product
|
||||
restore — put 9202 back on the saved config (the live catalog) and print the controls
|
||||
|
||||
Reads (each names the checklist row it answers):
|
||||
0.4/0.5/1.7/1.8 the image's own env defaults, the container's env NAMES (values never printed except the named
|
||||
non-secret switches), Django's DEBUG, the settings' env reads
|
||||
1.5 the server process inside the container
|
||||
3.5 the default login polled once a second from the install press, through traefik, as a stranger
|
||||
4.3 press → healthy, RestartCount
|
||||
5.1 the wger container's cgroup from its birth, every 2 s: anon, swap, oom_kill (swap reported, so a
|
||||
pass on swap is visible)
|
||||
3.9/0.7 the browser form (https Origin + CSRF) and the phone app's route, right and wrong
|
||||
1.8 an unknown page through traefik: a Django debug page or not
|
||||
2.8 a progress photo uploaded through the API and fetched back through traefik
|
||||
2.7 the volumes' size after the seed
|
||||
4.4 negative control: the container paused → does the controller read it unhealthy?
|
||||
2.6 remove through the product → what is left
|
||||
Secrets: the generated admin password lives in this process and a 0600 file in SC only; nothing prints it.
|
||||
"""
|
||||
import json, os, re, sys, time, tempfile, secrets, base64, io, subprocess
|
||||
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"]
|
||||
VOL = "/var/lib/docker/volumes/felhom-controller-data/_data"
|
||||
DRILL_REPO = "https://gitea.dooplex.hu/admin/app-catalog-drill.git"
|
||||
SAVE = f"{VOL}/controller.yaml.pre-checklist1001"
|
||||
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")
|
||||
|
||||
|
||||
def creds():
|
||||
for l in io.open(os.path.expanduser("~/.git-credentials")).read().strip().split("\n"):
|
||||
m = re.match(r'https://(admin):([^@]+)@gitea\.dooplex\.hu', l)
|
||||
if m:
|
||||
return m.group(1), m.group(2)
|
||||
raise SystemExit("no admin credential")
|
||||
|
||||
|
||||
def to_drill():
|
||||
u, t = creds()
|
||||
p(w.guest(f"""
|
||||
set -e
|
||||
test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE}
|
||||
python3 - <<'PY'
|
||||
import re
|
||||
p = "{VOL}/controller.yaml"
|
||||
s = open(p).read()
|
||||
s = re.sub(r'(^\\s+repo_url: ).*$', r'\\g<1>{DRILL_REPO}', s, count=1, flags=re.M)
|
||||
s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+token: ).*$', r'\\g<1>"{t}"', s, count=1, flags=re.M)
|
||||
s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+username: ).*$', r'\\g<1>"{u}"', s, count=1, flags=re.M)
|
||||
open(p, "w").write(s)
|
||||
PY
|
||||
rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache
|
||||
docker restart felhom-controller >/dev/null
|
||||
sleep 20
|
||||
echo "saved copy: $(ls -l {SAVE} | awk '{{print $5, $9}}')"
|
||||
grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: <redacted>/'
|
||||
"""))
|
||||
w.login()
|
||||
w.sync_rescan()
|
||||
controls("drill")
|
||||
dump("C0-repoint-drill.txt")
|
||||
|
||||
|
||||
def controls(want):
|
||||
head = w.guest(f"cd {VOL}/data/catalog-cache 2>/dev/null && git log --oneline -1 && git remote get-url origin | sed 's#//[^@]*@#//#'").strip()
|
||||
p("CONTROL 1 — the box's catalog clone:", head)
|
||||
live = subprocess.run(["git", "ls-remote", "https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git", "refs/heads/main"],
|
||||
capture_output=True, text=True).stdout.split()[:1]
|
||||
p("CONTROL 2 — the LIVE catalog main (unchanged by this act):", live)
|
||||
p("CONTROL 3 — expected source:", want)
|
||||
|
||||
|
||||
def restore():
|
||||
p(w.guest(f"""
|
||||
set -e
|
||||
cp -p {SAVE} {VOL}/controller.yaml
|
||||
rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache
|
||||
docker restart felhom-controller >/dev/null
|
||||
sleep 20
|
||||
grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: <redacted>/'
|
||||
"""))
|
||||
w.login()
|
||||
w.sync_rescan()
|
||||
controls("live")
|
||||
p("leftovers named wger:", w.guest("docker ps -a --format '{{.Names}}' | grep -i wger; docker volume ls -q | grep -i wger; ls -d /opt/docker/stacks/wger 2>/dev/null").strip() or "none")
|
||||
dump("C9-restore-live.txt")
|
||||
|
||||
|
||||
SAMPLER = r"""
|
||||
cat > /root/wger-sampler.sh <<'SH'
|
||||
#!/bin/bash
|
||||
out=/root/wger-mem.csv; : > $out
|
||||
t0=$(date +%s)
|
||||
id=""
|
||||
while [ $(( $(date +%s) - t0 )) -lt 1200 ]; do
|
||||
if [ -z "$id" ]; then id=$(docker ps -aq --no-trunc --filter name=^wger$ | head -1); fi
|
||||
if [ -n "$id" ]; then
|
||||
cg=$(ls -d /sys/fs/cgroup/system.slice/docker-$id.scope 2>/dev/null || find /sys/fs/cgroup -maxdepth 4 -type d -name "*$id*" 2>/dev/null | head -1)
|
||||
if [ -n "$cg" ] && [ -f "$cg/memory.stat" ]; then
|
||||
anon=$(awk '$1=="anon"{print $2}' $cg/memory.stat)
|
||||
cur=$(cat $cg/memory.current); sw=$(cat $cg/memory.swap.current 2>/dev/null || echo NA)
|
||||
oom=$(awk '$1=="oom_kill"{print $2}' $cg/memory.events); lim=$(cat $cg/memory.max)
|
||||
rc=$(docker inspect -f '{{.RestartCount}} {{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' $id 2>/dev/null)
|
||||
echo "$(date +%s.%N | cut -c1-14),$anon,$cur,$sw,$oom,$lim,$rc" >> $out
|
||||
fi
|
||||
fi
|
||||
[ -f /root/wger-sampler.stop ] && break
|
||||
sleep 2
|
||||
done
|
||||
SH
|
||||
chmod +x /root/wger-sampler.sh; rm -f /root/wger-sampler.stop
|
||||
nohup /root/wger-sampler.sh >/dev/null 2>&1 &
|
||||
echo sampler started
|
||||
"""
|
||||
|
||||
POLLER = r"""
|
||||
cat > /root/wger-poll.sh <<'SH'
|
||||
#!/bin/bash
|
||||
# a STRANGER: no dashboard session, no gate cookie — the public default login, once a second, through traefik
|
||||
out=/root/wger-poll.txt; : > $out
|
||||
t0=$(date +%s)
|
||||
while [ $(( $(date +%s) - t0 )) -lt 600 ]; do
|
||||
r=$(curl -sk --max-time 4 -o /root/wger-poll.body -w '%{http_code}' -H 'Host: fitness.enkisfelhom.hu' \
|
||||
-H 'Content-Type: application/json' --data '{"username":"admin","password":"adminadmin"}' \
|
||||
https://127.0.0.1/allauth/app/v1/auth/login)
|
||||
b=$(head -c 90 /root/wger-poll.body | tr '\n' ' ')
|
||||
echo "$(date +%H:%M:%S) $r $b" >> $out
|
||||
# the first answer that is the APP's own JSON (not the gate, not traefik's 404) ends the poll: every further
|
||||
# wrong try would count toward wger's 5-failure lock (decision 58) and spoil the household's login below
|
||||
case "$b" in *'"status"'*|*access_token*) break;; esac
|
||||
sleep 1
|
||||
done
|
||||
SH
|
||||
chmod +x /root/wger-poll.sh
|
||||
nohup /root/wger-poll.sh >/dev/null 2>&1 &
|
||||
echo poller started
|
||||
"""
|
||||
|
||||
|
||||
def walk():
|
||||
w.login()
|
||||
p("##### wger on 9202 — controller", w.guest("docker inspect felhom-controller --format {{.Config.Image}}").strip())
|
||||
p("catalog clone:", w.guest(f"cd {VOL}/data/catalog-cache && git log --oneline -1").strip())
|
||||
p("before: stack/volumes named wger:", w.guest("ls -d /opt/docker/stacks/wger 2>/dev/null; docker volume ls -q | grep -i wger").strip() or "none")
|
||||
p("guest swap (free -m):", " ".join(w.guest("free -m | grep -i swap").split()))
|
||||
p(w.guest(SAMPLER).strip())
|
||||
p(w.guest(POLLER).strip())
|
||||
t_press = time.time()
|
||||
p("deploy press at", time.strftime("%H:%M:%S"))
|
||||
ok = w.deploy("wger", SUB)
|
||||
p("deploy ->", ok)
|
||||
opened = None
|
||||
for _ in range(120):
|
||||
time.sleep(5)
|
||||
lg = w.guest("docker logs --since 30m felhom-controller 2>&1 | grep -E 'wger: install hold OPENED|wger.*after_install' | tail -3")
|
||||
if "hold OPENED" in lg:
|
||||
opened = lg.strip()
|
||||
break
|
||||
p("controller log (after_install / hold):", opened)
|
||||
healthy = None
|
||||
for _ in range(90):
|
||||
h = w.guest("docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}} {{.RestartCount}}' wger").strip()
|
||||
if h.startswith("healthy"):
|
||||
healthy = time.time() - t_press
|
||||
break
|
||||
time.sleep(2)
|
||||
p("4.3 press -> docker healthy: %.0f s; RestartCount: %s; start_period in compose: %s" % (
|
||||
healthy or -1, w.guest("docker inspect -f '{{.RestartCount}}' wger").strip(),
|
||||
w.guest("grep -m1 start_period /opt/docker/stacks/wger/docker-compose.yml").strip()))
|
||||
p(" controller state:", w.stack("wger").get("state"))
|
||||
time.sleep(8)
|
||||
w.guest("pkill -f wger-poll.sh || true")
|
||||
poll = w.guest("cat /root/wger-poll.txt").strip().splitlines()
|
||||
codes = {}
|
||||
for l in poll:
|
||||
c = l.split(" ")[1] if len(l.split(" ")) > 1 else "?"
|
||||
codes[c] = codes.get(c, 0) + 1
|
||||
p("3.5 stranger polls of admin/adminadmin from the press: %d tries; codes %s" % (len(poll), codes))
|
||||
p(" first 3:", poll[:3])
|
||||
p(" last 3:", poll[-3:])
|
||||
p(" any 200 with a token:", any(" 200 " in l and "access_token" in l for l in poll))
|
||||
dump("C1-install.txt")
|
||||
|
||||
# --- the static reads inside the container
|
||||
OUT.clear()
|
||||
p("1.5 the server process:", " | ".join(w.guest("docker exec wger sh -c \"ps -eo args 2>/dev/null || cat /proc/[0-9]*/cmdline | tr '\\\\0' ' '\" | grep -E '[m]anage.py|[g]unicorn|[u]vicorn|[d]aphne' | head -4").strip().splitlines()))
|
||||
p("1.7 image entrypoint:", w.guest("docker image inspect wger/server:2.7 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'").strip())
|
||||
p("1.7 entrypoint switches (if-tests in /home/wger/entrypoint.sh):")
|
||||
p(w.guest("docker exec wger grep -nE 'if \\[\\[? *\"?\\$' /home/wger/entrypoint.sh").rstrip())
|
||||
p("0.4/1.7 the IMAGE's env defaults for those switches (+ debug/mail/sync):")
|
||||
env_img = w.guest("docker image inspect wger/server:2.7 --format '{{range .Config.Env}}{{println .}}{{end}}'")
|
||||
keep = re.compile(r"^(DJANGO_DEBUG|DJANGO_PERFORM_MIGRATIONS|WGER_USE_GUNICORN|SYNC_\w+|DOWNLOAD_\w+|LOAD_ONLINE\w*|"
|
||||
r"DJANGO_COLLECTSTATIC\w*|ENABLE_EMAIL|EXERCISE\w*|ALLOW_\w+|USE_CELERY|DJANGO_CLEAR\w*|YARN\w*|TZ)=")
|
||||
for l in env_img.splitlines():
|
||||
if keep.match(l):
|
||||
p(" image:", l)
|
||||
p(" the RUNNING container's env, the same switches (values: only these named, non-secret ones):")
|
||||
env_run = w.guest("docker inspect wger --format '{{range .Config.Env}}{{println .}}{{end}}'")
|
||||
for l in env_run.splitlines():
|
||||
if keep.match(l) or l.startswith(("AXES_", "CSRF_TRUSTED", "X_FORWARDED", "SITE_URL")):
|
||||
p(" container:", l)
|
||||
names = sorted({l.split("=", 1)[0] for l in env_run.splitlines() if "=" in l})
|
||||
p(" container env NAMES (all):", " ".join(names))
|
||||
p("1.6 the env names wger's settings READ (settings/main.py, env.*('NAME'...)):")
|
||||
reads = w.guest("docker exec wger sh -c \"grep -ohE \\\"env\\\\.[a-z]+\\\\('[A-Z_0-9]+'\\\" /home/wger/src/settings/*.py | sed -E \\\"s/.*\\\\('//; s/'$//\\\" | sort -u\"").split()
|
||||
p(" ", " ".join(reads))
|
||||
secretish = [r for r in reads if re.search(r"KEY|SECRET|TOKEN|PASSWORD|PEM", r)]
|
||||
p(" of which key/secret-like:", secretish)
|
||||
p(" set in the container:", [r for r in secretish if r in names], " NOT set:", [r for r in secretish if r not in names])
|
||||
dj = w.guest("""cat > /tmp/djs.py <<'PY'
|
||||
import os, sys
|
||||
sys.path.insert(0, '/home/wger/src'); os.chdir('/home/wger/src')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main')
|
||||
import django; django.setup()
|
||||
from django.conf import settings as s
|
||||
print('DEBUG', s.DEBUG)
|
||||
print('EMAIL_BACKEND', getattr(s, 'EMAIL_BACKEND', None))
|
||||
print('WGER_SETTINGS sync/download:', {k: v for k, v in getattr(s, 'WGER_SETTINGS', {}).items() if any(x in k for x in ('SYNC', 'DOWNLOAD', 'USE_CELERY', 'ALLOW', 'EMAIL', 'TWITTER', 'MASTODON'))})
|
||||
print('JWT key loaded:', bool(os.environ.get('JWT_PRIVATE_KEY')), '(env in this shell is not the server env)')
|
||||
PY
|
||||
docker cp /tmp/djs.py wger:/tmp/djs.py && docker exec wger python3 /tmp/djs.py 2>&1 | tail -4""")
|
||||
p("1.8/0.4 Django settings inside the app:\n" + dj.rstrip())
|
||||
p("0.4/0.5 the container's first-start log lines about the network (sync/download/http):")
|
||||
p(w.guest("docker logs wger 2>&1 | grep -iE 'sync|download|http|fixture|ingredient|exercise' | head -12").rstrip())
|
||||
p(" outbound TCP connections the wger process holds right now:", w.guest(
|
||||
"docker exec wger sh -c 'cat /proc/net/tcp /proc/net/tcp6 2>/dev/null' | awk 'NR>1 && $4==\"01\"{print $3}' | sort | uniq -c | head").strip() or "none")
|
||||
dump("C2-static-reads.txt")
|
||||
|
||||
# --- logins, as each client does
|
||||
OUT.clear()
|
||||
pw = (w.GENERATED.get("wger") or {}).get("ADMIN_PASSWORD") or ""
|
||||
with io.open(os.path.join(os.environ["SC"], "wger.pw"), "w") as fh:
|
||||
fh.write(pw)
|
||||
os.chmod(os.path.join(os.environ["SC"], "wger.pw"), 0o600)
|
||||
W = fxb.Wger()
|
||||
jar = tempfile.mktemp(prefix="wger-jar-")
|
||||
hdr, why = W._login(w, SUB, pw, jar)
|
||||
p("3.9 browser form, https Origin + CSRF, RIGHT password:", why)
|
||||
jar2 = tempfile.mktemp(prefix="wger-jar2-")
|
||||
hdr2, why2 = W._login(w, SUB, pw + "x", jar2)
|
||||
p("3.9 browser form, WRONG password:", why2, "(refused)" if hdr2 is None else "(LET IN!)")
|
||||
for label, pwd in (("RIGHT", pw), ("WRONG", pw + "y")):
|
||||
rc, code, out = w.app_curl(SUB, "/allauth/app/v1/auth/login", "-H", "Content-Type: application/json",
|
||||
data=json.dumps({"username": "admin", "password": pwd}), method="POST")
|
||||
keys = []
|
||||
try:
|
||||
keys = sorted((json.loads(out).get("meta") or {}).keys()) or sorted(json.loads(out).keys())
|
||||
except Exception:
|
||||
pass
|
||||
p(f"3.9/0.7 phone-app route /allauth/app/v1/auth/login, {label} password: http={code} keys={keys}")
|
||||
if label == "RIGHT" and code == "200":
|
||||
tok = (json.loads(out).get("meta") or {}).get("access_token") or ""
|
||||
rc, c2, _ = w.app_curl(SUB, "/api/v2/weightentry/", "-H", f"Authorization: Bearer {tok}")
|
||||
p(" the API with that token: http=%s" % c2)
|
||||
# --- 1.8 an unknown page
|
||||
rc, code, body = w.app_curl(SUB, "/felhom-no-such-page-xyz/")
|
||||
p("1.8 unknown page through traefik: http=%s debug-page=%s traceback=%s" % (
|
||||
code, "DEBUG = True" in body, "Traceback" in body))
|
||||
dump("C3-logins.txt")
|
||||
|
||||
# --- seed (the fixture's own front door) + a photo
|
||||
OUT.clear()
|
||||
say = lambda *a: p(*a)
|
||||
t = W.seed(w, SUB, say)
|
||||
p("seed:", {k: v for k, v in (t or {}).items() if k != "pw"})
|
||||
png = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
|
||||
fn = tempfile.mktemp(suffix=".png")
|
||||
open(fn, "wb").write(png)
|
||||
if hdr:
|
||||
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", method="POST")
|
||||
p("2.8 POST /api/v2/gallery/ (multipart, through traefik): http=%s %s" % (code, re.sub(r'"image":"[^"]*"', '"image":"…"', out[:160])))
|
||||
try:
|
||||
url = json.loads(out).get("image") or ""
|
||||
except Exception:
|
||||
url = ""
|
||||
if url:
|
||||
path = re.sub(r"^https?://[^/]+", "", url)
|
||||
rc, code, body = w.app_curl(SUB, path, *hdr)
|
||||
p("2.8 GET the uploaded image back through traefik (%s): http=%s bytes=%d same=%s" % (
|
||||
path, code, len(body.encode("latin-1", "ignore")), "PNG" in body[:8]))
|
||||
rc, code, body = w.app_curl(SUB, path)
|
||||
p("2.8 the same image with NO session: http=%s" % code)
|
||||
p("verify (fixture readback, with its negative controls):", W.verify(w, SUB, t, say) if t else "no seed")
|
||||
p("2.7 the volumes' size after the seed:", " ".join(w.guest(
|
||||
"for v in $(docker volume ls -q | grep -i wger); do du -sh $(docker volume inspect -f '{{.Mountpoint}}' $v) | awk -v v=$v '{print v\"=\"$1}'; done").split()))
|
||||
dump("C4-seed-photo-size.txt")
|
||||
|
||||
# --- 4.4 negative control
|
||||
OUT.clear()
|
||||
p("4.4 negative control: docker pause wger at", time.strftime("%H:%M:%S"), "— controller state before:", w.stack("wger").get("state"))
|
||||
w.guest("docker pause wger")
|
||||
seen = []
|
||||
for i in range(45):
|
||||
time.sleep(4)
|
||||
st = w.stack("wger")
|
||||
s = (st.get("state"), (st.get("health") or {}).get("status") if isinstance(st.get("health"), dict) else st.get("health"))
|
||||
if not seen or seen[-1][1] != s:
|
||||
seen.append((round(4 * (i + 1)), s))
|
||||
if s[0] in ("unhealthy", "degraded", "stopped", "error"):
|
||||
break
|
||||
p(" states seen while paused (s, (state, health)):", seen)
|
||||
w.guest("docker unpause wger")
|
||||
back = None
|
||||
for i in range(45):
|
||||
time.sleep(4)
|
||||
if w.stack("wger").get("state") == "running":
|
||||
back = 4 * (i + 1)
|
||||
break
|
||||
p(" after unpause, state running again after %s s" % back)
|
||||
dump("C5-negative-control.txt")
|
||||
|
||||
# --- 5.1 the sampler
|
||||
OUT.clear()
|
||||
w.guest("touch /root/wger-sampler.stop")
|
||||
time.sleep(3)
|
||||
csv = w.guest("cat /root/wger-mem.csv").strip().splitlines()
|
||||
rows = [l.split(",") for l in csv if l.count(",") >= 6]
|
||||
if rows:
|
||||
anon = [int(r[1]) for r in rows if r[1].isdigit()]
|
||||
sw = [int(r[3]) for r in rows if r[3].isdigit()]
|
||||
oom = [int(r[4]) for r in rows if r[4].isdigit()]
|
||||
lim = rows[0][5]
|
||||
p("5.1 wger cgroup from birth: %d samples over %.0f s; limit %s" % (len(rows), float(rows[-1][0]) - float(rows[0][0]), lim))
|
||||
p(" peak anon %.1f MiB (%.1f %% of the limit); peak swap %s bytes; oom_kill max %s; last: %s" % (
|
||||
max(anon) / 1048576, 100.0 * max(anon) / int(lim) if lim.isdigit() else -1, max(sw) if sw else "NA", max(oom) if oom else "NA", rows[-1][6]))
|
||||
with io.open(os.path.join(EV, "C6-wger-mem.csv"), "w") as fh:
|
||||
fh.write("epoch,anon,current,swap,oom_kill,limit,restarts status health\n" + "\n".join(csv) + "\n")
|
||||
dump("C6-first-start-memory.txt")
|
||||
|
||||
# --- 2.6 remove through the product
|
||||
OUT.clear()
|
||||
c1, d1 = w.ctl("POST", "/api/stacks/wger/stop")
|
||||
p("2.6 stop ->", c1)
|
||||
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": False, "remove_backups": True})
|
||||
p("2.6 remove, KEEP drive data (wger has no drive 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")
|
||||
w.guest("rm -f /root/wger-sampler.sh /root/wger-poll.sh /root/wger-poll.body /root/wger-sampler.stop /tmp/djs.py; docker exec felhom-controller true")
|
||||
p(" the image (kept per decision 53 until the sweep):", w.guest("docker images --format '{{.Repository}}:{{.Tag}}' | grep -i wger").strip())
|
||||
dump("C7-remove.txt")
|
||||
for j in (jar, jar2, fn):
|
||||
if os.path.exists(j):
|
||||
os.unlink(j)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
{"drill": to_drill, "walk": walk, "restore": restore}[sys.argv[1]]()
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/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)
|
||||
@@ -0,0 +1,74 @@
|
||||
# Onboarding record — wger, AS IT WAS ON 2026-09-29 (the pilot's first pass)
|
||||
|
||||
app: wger
|
||||
opened: 2026-10-01
|
||||
template_at: d0e7e2e (2026-09-29 09:19 — the last wger commit before the four fixes; copy in B/template-d0e7e2e/)
|
||||
|
||||
<!--
|
||||
The pilot (TASK Part C): fill the record against the template as it stood BEFORE this week's four wger fixes, using
|
||||
evidence already in the audits (much of it measured on 2026-09-30/10-01 against this same template) and asking:
|
||||
would the checklist have caught R-737, R-738, R-752, R-755? A row that FAILS on the old template is written `open`
|
||||
with "FAILS:" — that is the row catching the defect. A row with no evidence for this template version is `open`
|
||||
with "not measured". Paths are from the workspace root. Same format as app-catalog-felhom.eu/onboarding/_TEMPLATE.md.
|
||||
-->
|
||||
|
||||
0.1 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — AGPL-3.0, image pulled from Docker Hub
|
||||
0.2 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — 2.7 on 2026-09-03, 2.6 on 2026-06-17; repo pushed 2026-10-01
|
||||
0.3 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — plain `x.y` tags, amd64 + arm64
|
||||
0.4 | open | not measured for this template on 2026-09-29: the entrypoint's start-time network jobs (exercise sync, image downloads) were never listed — see the now-record C2
|
||||
0.5 | open | not measured for this template on 2026-09-29 (same read as 0.4)
|
||||
0.6 | n/a | wger serves only HTTP on port 8000; nothing else is published
|
||||
0.7 | open | FAILS: the phone app's login route `/allauth/app/v1/auth/login` answered 500 on the right password (R-737) — felhom.eu/documentation/audits/more-night-apps-2026-09-30/B/wger-probe.txt
|
||||
0.8 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/.felhom.yml — tagline + five use_cases
|
||||
0.9 | done | felhom.eu/documentation/audits/more-night-apps-2026-09-30/bench/apps/wger/bench/evidence/MV-wger/verdict.json — the bench ran it; SITE_URL builds links only
|
||||
1.1 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B2-gates-at-d0e7e2e.txt — image-pins exit 0 at d0e7e2e
|
||||
1.2 | n/a | wger keeps SQLite on its own volume, no database sidecar
|
||||
1.3 | n/a | no MariaDB or PostgreSQL service in this template
|
||||
1.4 | open | FAILS: no `DJANGO_PERFORM_MIGRATIONS`; 2.6 -> 2.7 ended `done`, login 500, 12 migrations unapplied (R-738) — felhom.eu/documentation/audits/more-night-apps-2026-09-30/box/wger/step-attempt1-FAILED-R738.txt
|
||||
1.5 | open | FAILS: the template sets no `WGER_USE_GUNICORN`, so the entrypoint runs `manage.py runserver` (R-755) — felhom.eu/documentation/audits/lockouts-2026-10-01/B/B4-wger-fix-drill.txt
|
||||
1.6 | open | FAILS: settings read `JWT_PRIVATE_KEY` (settings/main.py:109), the template sets none (R-737) — felhom.eu/documentation/audits/more-night-apps-2026-09-30/B/wger-probe.txt
|
||||
1.7 | open | FAILS: the entrypoint's switches `DJANGO_PERFORM_MIGRATIONS`, `WGER_USE_GUNICORN` and `DJANGO_DEBUG` were never read or decided — the last one means `collectstatic` never runs and no CSS/JS is served (R-762, measured 2026-10-01 on the current template; this line is unchanged since d0e7e2e) — felhom.eu/documentation/audits/more-night-apps-2026-09-30/box/wger/entrypoint-read.txt
|
||||
1.8 | open | not measured for this template on 2026-09-29 (the now-record C2 reads DEBUG on the same image line)
|
||||
1.9 | n/a | SECRET_KEY signs sessions and reset links only; losing it means signing in again, no data is lost
|
||||
2.1 | done | app-catalog-felhom.eu/audits/persistence-sweep-2026-08-02/state/gate.log — wger CLEAN (2026-08-02, same volumes)
|
||||
2.2 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/docker-compose.yml — two named volumes (NVMe), no drive path
|
||||
2.3 | n/a | needs_hdd false: no drive path to classify; the tier-1 unit holds both named volumes
|
||||
2.4 | done | felhom.eu/documentation/audits/more-night-apps-2026-09-30/bench/apps/wger/bench/evidence/MV-wger/verdict.json — started and wrote its database on the bench
|
||||
2.5 | open | not measured: no backup -> remove -> restore -> read back of wger exists in any audit
|
||||
2.6 | open | not measured for wger
|
||||
2.7 | open | not measured for wger
|
||||
2.8 | open | FAILS: an uploaded photo is saved but answers 404 — nothing serves /media/ (R-762, measured 2026-10-01 on the current template; unchanged since d0e7e2e) — felhom.eu/documentation/audits/new-app-checklist-2026-10-01/C/C8-signup-guest-media-static.txt
|
||||
3.1 | done | felhom.eu/documentation/audits/login-gate-2026-09-29/D/D2-live.txt — class 3 measured on 9202 (FIRST-ADMIN row)
|
||||
3.2 | done | felhom.eu/documentation/audits/login-gate-2026-09-29/D/D2-live.txt — default refused, generated signs in, wrong refused
|
||||
3.3 | n/a | class 3: a known default login, not an open first-run screen
|
||||
3.4 | open | FAILS: a stranger signs up after the setup, and each anonymous dashboard visit makes a guest account (R-763, measured 2026-10-01 on the current template; `ALLOW_REGISTRATION`/`ALLOW_GUEST_USERS` unset since d0e7e2e) — felhom.eu/documentation/audits/new-app-checklist-2026-10-01/C/C8-signup-guest-media-static.txt
|
||||
3.5 | open | not measured for wger (R-741 was measured on calibre-web and mealie)
|
||||
3.6 | open | FAILS: 10 wrong tries by a stranger locked every member out for 30 min — django-axes keyed on the address, and behind the tunnel everyone has one (R-752, R-753) — felhom.eu/documentation/audits/lockouts-2026-10-01/B/B2-wger-control-live-template.txt
|
||||
3.7 | open | not measured: no `add_people` text in this template
|
||||
3.8 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/.felhom.yml — the password is `sys.argv[1]`
|
||||
3.9 | open | FAILS (half): the browser form works with the https Origin (R-712 fixed in this commit), the phone app's route answers 500 (R-737) — felhom.eu/documentation/audits/more-night-apps-2026-09-30/B/wger-probe.txt
|
||||
4.1 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/docker-compose.yml — wget to 127.0.0.1:8000; the image has wget (healthy on the bench)
|
||||
4.2 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B2-gates-at-d0e7e2e.txt — probe-matches-compose exit 0 at d0e7e2e
|
||||
4.3 | done | felhom.eu/documentation/audits/more-night-apps-2026-09-30/bench/apps/wger/bench/evidence/MV-wger/run.log — healthy at FROM on the bench
|
||||
4.4 | open | not measured for wger
|
||||
4.5 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/docker-compose.yml — `container_name: wger`, the only service
|
||||
5.1 | open | not measured: no first-start-from-birth watch of wger exists (the bench watch is after the update)
|
||||
5.2 | done | felhom.eu/documentation/audits/more-night-apps-2026-09-30/bench/apps/wger/bench/evidence/MV-wger/memory-samples.json — 10 min, peak anon 49.8 % of 384M, 0 kills (at TO, 2.7)
|
||||
5.3 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/template-d0e7e2e/.felhom.yml — mem_limit 384M = the one service's 384M
|
||||
5.4 | n/a | wger is a Python (Django) app, no self-sizing heap
|
||||
5.5 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — pi_compatible true, arm64 published, ~373 MB
|
||||
6.1 | open | not done on 2026-09-29: no wger fixture existed; the one written on 2026-09-30 is what caught R-738
|
||||
6.2 | open | not done on 2026-09-29: no ladder entry
|
||||
6.3 | open | not measured for wger
|
||||
6.4 | open | not measured on 2026-09-29 (the 2026-09-30 step marked nothing)
|
||||
6.5 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — plain `x.y` plus `x.y.0`; the shape held 2.1 -> 2.7
|
||||
7.1 | open | FAILS: wger has a mail switch and no `smtp_mapping`; its mail goes to the console (R-764, read 2026-10-01; unchanged since d0e7e2e) — felhom.eu/documentation/audits/new-app-checklist-2026-10-01/C/C2-static-reads.txt
|
||||
8.1 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B2-gates-at-d0e7e2e.txt — copy-i18n exit 0 at d0e7e2e (wger 20/20)
|
||||
8.2 | open | not read on 9202's page for this version
|
||||
8.3 | done | felhom.eu/documentation/audits/new-app-checklist-2026-10-01/B/B1-upstream-reads.txt — wger-logo.png and screenshot-1 answer 200
|
||||
8.4 | done | app-catalog-felhom.eu/README.md — row `fitness.*`; FIRST-ADMIN row; category home; catalog_since 2026-07-19
|
||||
8.5 | n/a | wger is an existing app; the count does not change
|
||||
9.1 | open | only the static gates were re-run at d0e7e2e (B2); the runtime gates were not
|
||||
9.2 | done | felhom.eu/documentation/audits/login-gate-2026-09-29/D/D2-live.txt — fresh install on 9202 as household and stranger (2026-09-29)
|
||||
9.3 | open | 26 other rows are open, 10 of them FAILS
|
||||
9.4 | n/a | wger is exempt: published 2026-02-15, before the checklist
|
||||
Reference in New Issue
Block a user