2026-10-08 day: legal drafts (R-813), R-304 design, R-232(a) evidence, R-762 bench proof; R-899/R-243/R-304/R-232/R-762 updated; R-900, R-901 opened; 127 -> 130 (R-902 by the website session)
gates / gates (push) Successful in 3m43s
gates / gates (push) Successful in 3m43s
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,50 @@
|
||||
# R-304 — the household's old recovery code and the retained packages: a one-page design (2026-10-08)
|
||||
|
||||
**Status:** design only. Nothing here is built except today's honesty fix (below). Customer data and promises are the
|
||||
operator's: they are the two questions at the end.
|
||||
|
||||
## Where it stands (read in source today, not from the row)
|
||||
|
||||
- **Retention works** and the material opens the old store (drill 2026-08-12, `audits/DRILL-retained-key-2026-08-12.md`).
|
||||
- **R-311 shipped** (hub v0.103.0, agent v0.129.0, controller v0.214.0): after the current package refuses a code, the
|
||||
agent fetches the retained packages (`GET /hosts/<id>/escrow/retained`, self-scoped, cap 16) and tries up to 6. If one
|
||||
opens, the screen says „the code is correct, it opens an earlier package; contact support" (HTTP 422).
|
||||
- **R-312 is DECIDED (2026-08-13): no in-product route from the recovery screen to a set-aside store** — „re-evaluate on
|
||||
a real customer request". So retention is an **operator-only** capability today, by decision.
|
||||
- **What was still false until today:** the agent answered „the code did not open the sealed bundle" (400) also when it
|
||||
had NOT tried every earlier package — the hub withheld rows (no key material, over the cap), a package was malformed,
|
||||
the 6-try cap stopped the loop, or the retained list could not be read. **Fixed on main today** (agent 424
|
||||
`older_unchecked`, controller `RecoveryOlderUnchecked`, Hungarian + English: „we do not know whether your code is
|
||||
wrong … contact support"). Ships with tomorrow's releases. The fix is in the agent and the controller, not the hub:
|
||||
the hub never sees the code (zero-knowledge, `07` §2), so it cannot check a row; it already reports what it withheld
|
||||
(`unopenable_count`, `truncated_count`), and the agent now counts those.
|
||||
|
||||
## „Which package?" — the question is smaller than it looked
|
||||
|
||||
Each escrow ceremony seals with a NEW recovery code (the household is shown it once). A code opens only the package it
|
||||
sealed. So when a household holds several old codes, **each code selects its own package** — no list, no choice screen.
|
||||
The agent already tries newest-superseded first. The only real limits are the two caps (16 served, 6 tried), which
|
||||
today's fix turns from a silent „wrong code" into an honest „not all checked".
|
||||
|
||||
## Options for really serving the old copy
|
||||
|
||||
| | What | Costs | Customer data / promise |
|
||||
|---|---|---|---|
|
||||
| **A** | Keep it operator-only (R-312). The screen's „contact support" is the route. | Nothing more. The operator needs SQLite, `age` and a shell (the drill's §4) — slow, error-prone, undocumented as a runbook. | No change. |
|
||||
| **B** | In-product: when a retained package opens and carries a repository password, the screen offers a READ-ONLY browse of the old store (list + download), never a restore into place. | New surface: the old store's location (moved aside / orphaned, R-241), a second repository password in memory, a second browse path. ~2 sessions + a drill. | Changes a promise (the household can reach old history alone). **Reverses R-312** — operator only. |
|
||||
| **C** | Operator-assisted, first slice: when the agent answers 422 (opens retained) or 424 (not all checked), the controller sends ONE operator event naming the box and the package date; plus a runbook „open a retained package for a household" (the drill's §4 written down, the household types the code on its own box). | Small: one event type (operator-only), one runbook. ~½ session. | No new promise; makes the existing „contact support" true in practice. |
|
||||
|
||||
**Pick: C now; B only on R-312's own trigger** (a real customer asks). C makes the sentence the screen already says —
|
||||
„contact support" — something the operator can act on the same day, without reversing a decision.
|
||||
|
||||
**First slice of C:** controller: on `RecoveryCodeOpensRetained` / `RecoveryOlderUnchecked`, send `recovery_retained_needed`
|
||||
(operator-only, warning, once per box per day) with the package date and the class — never the code. Hub: allowlist +
|
||||
`operatorOnlyEvents` in the same commit. Docs: `runbooks/RUNBOOK-open-retained-package.md` from the drill's §4.
|
||||
|
||||
## Two questions for the operator
|
||||
|
||||
1. **May the product keep promising, in the capability map and the countdown banner, that old backups „stay
|
||||
recoverable"?** Today that is true only with your hands. *If you do nothing:* the promise stays worded as it is, and
|
||||
the honest route is „contact support" (A/C).
|
||||
2. **Do you want C's first slice built (an operator mail when a household's code opens — or may open — an old
|
||||
package)?** *If you do nothing:* nothing is built; you learn of such a household only when they write to you.
|
||||
@@ -0,0 +1,30 @@
|
||||
# R-232 (a) — DooPlex's backup mails the operator when it fails (2026-10-08)
|
||||
|
||||
**Operator word:** "Yes" in chat (2026-10-08, asked: "May I change DooPlex's backup notification so a failed run sends a
|
||||
mail? One setting, plus one test mail. I will not start a backup run.").
|
||||
|
||||
**What changed (DooPlex, unversioned scripts — R-231):**
|
||||
- `/opt/backup/scripts/backup-config.sh`: `notify_failure` now also sends a mail through Resend (the API the CI failure
|
||||
mail uses) from `monitoring@felhom.eu` to `admin@felhom.eu`. The webhook branch is unchanged. The mail never changes a
|
||||
backup's exit code (`return 0`) and logs its outcome to `backup.log`. Before/after: `backup-config.sh.before`,
|
||||
`backup-config.sh.after` (no secret in either). The old file is also kept beside it as
|
||||
`backup-config.sh.bak-20261008-080524`.
|
||||
- `/etc/backup/resend-api-key`: new, `600 root`, 36 bytes, copied from the k3s Secret `felhom-system/resend-api` with
|
||||
`umask 077` and never printed.
|
||||
- Nothing else in DooPlex's backup changed. **No backup run was started.**
|
||||
|
||||
**Proof (two channels):**
|
||||
1. The function's own output (`test-mail.txt`): `sudo bash -c 'source …/backup-config.sh; notify_failure "TEST - R-232
|
||||
wiring check, no backup ran"'` → `notify_failure: mail accepted id=01a11a1d-…`, `rc=0`, and the line
|
||||
`[INFO] notify_failure: failure mail sent to admin@felhom.eu` in `backup.log`.
|
||||
2. The inbox (Gmail connector, which reads the admin@ catch-all): one message, 2026-10-08T06:05:25Z, from
|
||||
`monitoring@felhom.eu`, subject `[DooPlex backup] FAILED: TEST - R-232 wiring check, no backup ran`, label INBOX.
|
||||
|
||||
**Not proven:** a real failure path end to end (no backup was forced to fail, by the brief). The callers are the
|
||||
existing `ERR` traps and `backup-all.sh`'s component check, unchanged.
|
||||
|
||||
**Rollback:** `sudo cp -p /opt/backup/scripts/backup-config.sh.bak-20261008-080524 /opt/backup/scripts/backup-config.sh`
|
||||
and `sudo rm /etc/backup/resend-api-key`.
|
||||
|
||||
**If the Resend key is rotated:** this file must be refreshed too (a second consumer of `Secret/resend-api`, beside the
|
||||
hub and contact-mailer).
|
||||
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
# Dooplex Cluster Backup Configuration
|
||||
# Source this file in backup scripts: source /opt/backup/backup-config.sh
|
||||
|
||||
# ============================================================================
|
||||
# BACKUP DESTINATIONS
|
||||
# ============================================================================
|
||||
export BACKUP_BASE="/mnt/5_hdd/backup"
|
||||
export BACKUP_K3S="${BACKUP_BASE}/k3s"
|
||||
export BACKUP_SECRETS="${BACKUP_BASE}/secrets"
|
||||
export BACKUP_MANIFESTS="${BACKUP_BASE}/homelab-manifests"
|
||||
# NOT under BACKUP_BASE. DATA_SOURCE_DIR moved to /mnt/5_hdd/data in the
|
||||
# 2026-08-14 migration off the failed 4_hdd, so leaving this repo under
|
||||
# BACKUP_BASE (also 5_hdd) would put the backup on the same physical disk as
|
||||
# its source -- protection against accidental deletion, none against loss of
|
||||
# sda1. 1_hdd holds no Longhorn replicas and only serves Plex reads, so backup
|
||||
# writes do not contend with live volume I/O.
|
||||
export BACKUP_DATA="/mnt/1_hdd/backup/data"
|
||||
export BACKUP_LONGHORN="${BACKUP_BASE}/longhorn-pvc"
|
||||
export BACKUP_LOGS="${BACKUP_BASE}/logs"
|
||||
|
||||
# ============================================================================
|
||||
# RESTIC REPOSITORIES (each category has its own repo for flexibility)
|
||||
# ============================================================================
|
||||
export RESTIC_REPO_K3S="${BACKUP_K3S}/restic-repo"
|
||||
export RESTIC_REPO_SECRETS="${BACKUP_SECRETS}/restic-repo"
|
||||
export RESTIC_REPO_DATA="${BACKUP_DATA}/restic-repo"
|
||||
export BACKUP_POSTGRESQL="${BACKUP_BASE}/postgresql"
|
||||
export POSTGRESQL_DUMP_DIR="${BACKUP_POSTGRESQL}/dumps"
|
||||
export RESTIC_REPO_POSTGRESQL="${BACKUP_POSTGRESQL}/restic-repo"
|
||||
|
||||
# ============================================================================
|
||||
# RESTIC PASSWORD (change this!)
|
||||
# Store in /etc/backup/restic-password or set RESTIC_PASSWORD_FILE
|
||||
# ============================================================================
|
||||
export RESTIC_PASSWORD_FILE="/etc/backup/restic-password"
|
||||
|
||||
# ============================================================================
|
||||
# RETENTION POLICY
|
||||
# ============================================================================
|
||||
export RETENTION_KEEP_LAST=7
|
||||
export RETENTION_KEEP_DAILY=7
|
||||
export RETENTION_KEEP_WEEKLY=4
|
||||
export RETENTION_KEEP_MONTHLY=6
|
||||
|
||||
# ============================================================================
|
||||
# SOURCE DIRECTORIES
|
||||
# ============================================================================
|
||||
export K3S_SERVER_DIR="/var/lib/rancher/k3s/server"
|
||||
export K3S_CONFIG_DIR="/etc/rancher/k3s"
|
||||
export DATA_SOURCE_DIR="/mnt/5_hdd/data"
|
||||
|
||||
# Claude Code auto-memory store (R-229, 2026-08-06). Rides in the User Data component because it is
|
||||
# small, exists on this host only, and is in NO git repository -- /mnt/5_hdd/felhom.eu/git is not a
|
||||
# repo, so nothing else preserves it. BACKED UP, NOT COMMITTED: it is auto-written and may name
|
||||
# hosts and paths that the project's secrets rule keeps out of committed files.
|
||||
# CAVEAT: BACKUP_BASE is on the SAME physical disk (/mnt/5_hdd) as this source, so this protects
|
||||
# against accidental deletion, NOT against loss of sda1.
|
||||
export CLAUDE_MEMORY_DIR="/mnt/5_hdd/felhom.eu/git/.claude-memory"
|
||||
|
||||
# ============================================================================
|
||||
# EXCLUDES
|
||||
# ============================================================================
|
||||
export DATA_EXCLUDES=(
|
||||
"*.tmp"
|
||||
"*.temp"
|
||||
"*.cache"
|
||||
"**/cache/**"
|
||||
"**/Cache/**"
|
||||
"**/.cache/**"
|
||||
"**/node_modules/**"
|
||||
"**/__pycache__/**"
|
||||
"**/Thumbs.db"
|
||||
"**/.DS_Store"
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# NOTIFICATION (optional - configure as needed)
|
||||
# ============================================================================
|
||||
export NOTIFY_ON_FAILURE="true"
|
||||
# export NOTIFY_WEBHOOK_URL="https://your-webhook-url"
|
||||
# R-232 (a), 2026-10-08 (operator yes in chat): a failed run is MAILED through the project's existing mail path
|
||||
# (Resend, the same API the CI failure mail uses) to the operator. The key is stored out-of-band, root-only, in
|
||||
# NOTIFY_RESEND_KEY_FILE (copied from the k3s Secret felhom-system/resend-api); it is never printed.
|
||||
export NOTIFY_RESEND_KEY_FILE="/etc/backup/resend-api-key"
|
||||
export NOTIFY_MAIL_FROM="DooPlex backup <monitoring@felhom.eu>"
|
||||
export NOTIFY_MAIL_TO="admin@felhom.eu"
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "${BACKUP_LOGS}/backup.log"
|
||||
}
|
||||
|
||||
log_info() { log "INFO" "$@"; }
|
||||
log_warn() { log "WARN" "$@"; }
|
||||
log_error() { log "ERROR" "$@"; }
|
||||
|
||||
check_restic() {
|
||||
if ! command -v restic &> /dev/null; then
|
||||
log_error "restic is not installed. Install with: apt install restic"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_kubectl() {
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
log_error "kubectl is not installed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_dirs() {
|
||||
mkdir -p "${BACKUP_K3S}" "${BACKUP_SECRETS}" "${BACKUP_MANIFESTS}" \
|
||||
"${BACKUP_DATA}" "${BACKUP_LONGHORN}" "${BACKUP_LOGS}" "${BACKUP_POSTGRESQL}"
|
||||
}
|
||||
|
||||
init_restic_repo() {
|
||||
local repo="$1"
|
||||
if [ ! -d "${repo}" ]; then
|
||||
log_info "Initializing restic repository: ${repo}"
|
||||
restic -r "${repo}" init
|
||||
fi
|
||||
}
|
||||
|
||||
apply_retention() {
|
||||
local repo="$1"
|
||||
log_info "Applying retention policy to ${repo}"
|
||||
restic -r "${repo}" forget \
|
||||
--keep-last ${RETENTION_KEEP_LAST} \
|
||||
--keep-daily ${RETENTION_KEEP_DAILY} \
|
||||
--keep-weekly ${RETENTION_KEEP_WEEKLY} \
|
||||
--keep-monthly ${RETENTION_KEEP_MONTHLY} \
|
||||
--prune
|
||||
}
|
||||
|
||||
notify_failure() {
|
||||
local message="$1"
|
||||
if [ "${NOTIFY_ON_FAILURE}" = "true" ] && [ -n "${NOTIFY_WEBHOOK_URL}" ]; then
|
||||
curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"text\": \"🚨 Backup Failed: ${message}\"}" \
|
||||
"${NOTIFY_WEBHOOK_URL}" || true
|
||||
fi
|
||||
# R-232 (a): the mail. Never fails the caller (a broken mail must not change a backup's exit code); logs its outcome.
|
||||
if [ "${NOTIFY_ON_FAILURE}" = "true" ] && [ -r "${NOTIFY_RESEND_KEY_FILE}" ]; then
|
||||
if NOTIFY_MSG="${message}" NOTIFY_HOST="$(hostname)" NOTIFY_LOG="${BACKUP_LOGS}/backup.log" python3 - <<'PY'
|
||||
import json, os, sys, urllib.error, urllib.request
|
||||
key = open(os.environ["NOTIFY_RESEND_KEY_FILE"]).read().strip()
|
||||
msg, host = os.environ.get("NOTIFY_MSG", ""), os.environ.get("NOTIFY_HOST", "?")
|
||||
body = json.dumps({
|
||||
"from": os.environ["NOTIFY_MAIL_FROM"], "to": [os.environ["NOTIFY_MAIL_TO"]],
|
||||
"subject": "[DooPlex backup] FAILED: %s" % msg,
|
||||
"text": "DooPlex's backup reported a failure.\n\nHost : %s\nFailure: %s\nLog : %s\n\n"
|
||||
"See journalctl -u dooplex-backup and the log above. This mail is sent by notify_failure "
|
||||
"in /opt/backup/scripts/backup-config.sh (R-232 a).\n" % (host, msg, os.environ.get("NOTIFY_LOG", "")),
|
||||
}).encode()
|
||||
req = urllib.request.Request("https://api.resend.com/emails", data=body, method="POST",
|
||||
headers={"Authorization": "Bearer %s" % key, "Content-Type": "application/json",
|
||||
# Cloudflare fronts api.resend.com and blocks the default Python-urllib agent (error 1010).
|
||||
"User-Agent": "dooplex-backup/1.0"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
print("notify_failure: mail accepted id=%s" % json.load(r).get("id"))
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit("notify_failure: Resend HTTP %s" % e.code)
|
||||
except Exception as e:
|
||||
sys.exit("notify_failure: mail not sent (%s)" % type(e).__name__)
|
||||
PY
|
||||
then log_info "notify_failure: failure mail sent to ${NOTIFY_MAIL_TO}"
|
||||
else log_error "notify_failure: the failure mail could NOT be sent"
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
# Dooplex Cluster Backup Configuration
|
||||
# Source this file in backup scripts: source /opt/backup/backup-config.sh
|
||||
|
||||
# ============================================================================
|
||||
# BACKUP DESTINATIONS
|
||||
# ============================================================================
|
||||
export BACKUP_BASE="/mnt/5_hdd/backup"
|
||||
export BACKUP_K3S="${BACKUP_BASE}/k3s"
|
||||
export BACKUP_SECRETS="${BACKUP_BASE}/secrets"
|
||||
export BACKUP_MANIFESTS="${BACKUP_BASE}/homelab-manifests"
|
||||
# NOT under BACKUP_BASE. DATA_SOURCE_DIR moved to /mnt/5_hdd/data in the
|
||||
# 2026-08-14 migration off the failed 4_hdd, so leaving this repo under
|
||||
# BACKUP_BASE (also 5_hdd) would put the backup on the same physical disk as
|
||||
# its source -- protection against accidental deletion, none against loss of
|
||||
# sda1. 1_hdd holds no Longhorn replicas and only serves Plex reads, so backup
|
||||
# writes do not contend with live volume I/O.
|
||||
export BACKUP_DATA="/mnt/1_hdd/backup/data"
|
||||
export BACKUP_LONGHORN="${BACKUP_BASE}/longhorn-pvc"
|
||||
export BACKUP_LOGS="${BACKUP_BASE}/logs"
|
||||
|
||||
# ============================================================================
|
||||
# RESTIC REPOSITORIES (each category has its own repo for flexibility)
|
||||
# ============================================================================
|
||||
export RESTIC_REPO_K3S="${BACKUP_K3S}/restic-repo"
|
||||
export RESTIC_REPO_SECRETS="${BACKUP_SECRETS}/restic-repo"
|
||||
export RESTIC_REPO_DATA="${BACKUP_DATA}/restic-repo"
|
||||
export BACKUP_POSTGRESQL="${BACKUP_BASE}/postgresql"
|
||||
export POSTGRESQL_DUMP_DIR="${BACKUP_POSTGRESQL}/dumps"
|
||||
export RESTIC_REPO_POSTGRESQL="${BACKUP_POSTGRESQL}/restic-repo"
|
||||
|
||||
# ============================================================================
|
||||
# RESTIC PASSWORD (change this!)
|
||||
# Store in /etc/backup/restic-password or set RESTIC_PASSWORD_FILE
|
||||
# ============================================================================
|
||||
export RESTIC_PASSWORD_FILE="/etc/backup/restic-password"
|
||||
|
||||
# ============================================================================
|
||||
# RETENTION POLICY
|
||||
# ============================================================================
|
||||
export RETENTION_KEEP_LAST=7
|
||||
export RETENTION_KEEP_DAILY=7
|
||||
export RETENTION_KEEP_WEEKLY=4
|
||||
export RETENTION_KEEP_MONTHLY=6
|
||||
|
||||
# ============================================================================
|
||||
# SOURCE DIRECTORIES
|
||||
# ============================================================================
|
||||
export K3S_SERVER_DIR="/var/lib/rancher/k3s/server"
|
||||
export K3S_CONFIG_DIR="/etc/rancher/k3s"
|
||||
export DATA_SOURCE_DIR="/mnt/5_hdd/data"
|
||||
|
||||
# Claude Code auto-memory store (R-229, 2026-08-06). Rides in the User Data component because it is
|
||||
# small, exists on this host only, and is in NO git repository -- /mnt/5_hdd/felhom.eu/git is not a
|
||||
# repo, so nothing else preserves it. BACKED UP, NOT COMMITTED: it is auto-written and may name
|
||||
# hosts and paths that the project's secrets rule keeps out of committed files.
|
||||
# CAVEAT: BACKUP_BASE is on the SAME physical disk (/mnt/5_hdd) as this source, so this protects
|
||||
# against accidental deletion, NOT against loss of sda1.
|
||||
export CLAUDE_MEMORY_DIR="/mnt/5_hdd/felhom.eu/git/.claude-memory"
|
||||
|
||||
# ============================================================================
|
||||
# EXCLUDES
|
||||
# ============================================================================
|
||||
export DATA_EXCLUDES=(
|
||||
"*.tmp"
|
||||
"*.temp"
|
||||
"*.cache"
|
||||
"**/cache/**"
|
||||
"**/Cache/**"
|
||||
"**/.cache/**"
|
||||
"**/node_modules/**"
|
||||
"**/__pycache__/**"
|
||||
"**/Thumbs.db"
|
||||
"**/.DS_Store"
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# NOTIFICATION (optional - configure as needed)
|
||||
# ============================================================================
|
||||
export NOTIFY_ON_FAILURE="true"
|
||||
# export NOTIFY_WEBHOOK_URL="https://your-webhook-url"
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "${BACKUP_LOGS}/backup.log"
|
||||
}
|
||||
|
||||
log_info() { log "INFO" "$@"; }
|
||||
log_warn() { log "WARN" "$@"; }
|
||||
log_error() { log "ERROR" "$@"; }
|
||||
|
||||
check_restic() {
|
||||
if ! command -v restic &> /dev/null; then
|
||||
log_error "restic is not installed. Install with: apt install restic"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_kubectl() {
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
log_error "kubectl is not installed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_dirs() {
|
||||
mkdir -p "${BACKUP_K3S}" "${BACKUP_SECRETS}" "${BACKUP_MANIFESTS}" \
|
||||
"${BACKUP_DATA}" "${BACKUP_LONGHORN}" "${BACKUP_LOGS}" "${BACKUP_POSTGRESQL}"
|
||||
}
|
||||
|
||||
init_restic_repo() {
|
||||
local repo="$1"
|
||||
if [ ! -d "${repo}" ]; then
|
||||
log_info "Initializing restic repository: ${repo}"
|
||||
restic -r "${repo}" init
|
||||
fi
|
||||
}
|
||||
|
||||
apply_retention() {
|
||||
local repo="$1"
|
||||
log_info "Applying retention policy to ${repo}"
|
||||
restic -r "${repo}" forget \
|
||||
--keep-last ${RETENTION_KEEP_LAST} \
|
||||
--keep-daily ${RETENTION_KEEP_DAILY} \
|
||||
--keep-weekly ${RETENTION_KEEP_WEEKLY} \
|
||||
--keep-monthly ${RETENTION_KEEP_MONTHLY} \
|
||||
--prune
|
||||
}
|
||||
|
||||
notify_failure() {
|
||||
local message="$1"
|
||||
if [ "${NOTIFY_ON_FAILURE}" = "true" ] && [ -n "${NOTIFY_WEBHOOK_URL}" ]; then
|
||||
curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"text\": \"🚨 Backup Failed: ${message}\"}" \
|
||||
"${NOTIFY_WEBHOOK_URL}" || true
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
notify_failure: mail accepted id=01a11a1d-d554-7f1f-acb7-ba5c268b70a6
|
||||
[2026-10-08 08:05:25] [INFO] notify_failure: failure mail sent to admin@felhom.eu
|
||||
rc=0
|
||||
[2026-10-08 03:14:36] [INFO] ========================================================
|
||||
[2026-10-08 08:05:25] [INFO] notify_failure: failure mail sent to admin@felhom.eu
|
||||
@@ -0,0 +1,69 @@
|
||||
# R-762 (wger's real web server: gunicorn with 2 workers): 2026-10-08 day, Part D
|
||||
|
||||
**Status: the bench half is done. The 9202 half is NOT done, so nothing was committed to the catalog.**
|
||||
To reach 9202, the drill catalog had to be brought up to date and wger un-hidden in it (wger is `lifecycle: hidden`,
|
||||
and the controller refuses to deploy a hidden app, `router.go` L461). The permission check refused that write
|
||||
(„Modify Shared Resources"). The brief says a refusal stops the item, so it stopped there. The drill repo, 9202's
|
||||
catalog setting and the live catalog were not changed. `app-catalog-felhom.eu` is clean at `32d1346`.
|
||||
|
||||
## The definition tested
|
||||
`definition-docker-compose.yml` is the current `templates/wger/docker-compose.yml` with two more env lines and a comment:
|
||||
`WGER_USE_GUNICORN=True` and `WEB_CONCURRENCY=2`.
|
||||
|
||||
Checked in the image `wger/server:2.7` (`sha256:1c5789b9…`, the same digest the ladder records) on bench 9401:
|
||||
- `/home/wger/entrypoint.sh` runs `gunicorn wger.wsgi:application --preload --bind 0.0.0.0:$PORT` only when
|
||||
`WGER_USE_GUNICORN == "True"`. Otherwise it runs `manage.py runserver`.
|
||||
- There is no `-w`, no `gunicorn.conf.py` in the working directory `/home/wger/src`, and no GUNICORN or WEB_ env in the
|
||||
image.
|
||||
- gunicorn 26.1.0's own `Config()` gives `workers` 1 by default and 2 with `WEB_CONCURRENCY=2`. The default timeout is
|
||||
30 s.
|
||||
|
||||
## Bench 9401 (demo-hp), two runs
|
||||
1. **`upgrade-test.py --soak 600 --move-to wger wger@gunicorn`** (harness v5): FROM the template as it stands (runserver)
|
||||
TO the gunicorn definition. Raw output: `bench/R762-gunicorn.log` and `bench/evidence/MV-wger/`.
|
||||
- The verdict is `proven`. The seed was read back before and after the switch. The app was healthy after it. The
|
||||
abort was `starts-and-serves`. Measured at 2026-10-08T06:19:56Z.
|
||||
- The memory watch ran for 606.5 s with 11,584 requests (all 302) and `load: reached`.
|
||||
- wger: anon peak 178,151,424 B = 170 MiB = **44.2 %** of 384M; 0 oom_kills; 0 restarts; the cgroup peak was
|
||||
100 % (page cache).
|
||||
- wger-files: anon peak 15.8 %; 0 kills; 0 restarts.
|
||||
- `to-full.log` has „Using gunicorn on port 8000..." and **2 × „Booting worker"** (pids 18 and 19).
|
||||
2. **`check/benchcheck.sh`**: the definition started fresh in project `r762b`, then the login page, its CSS, a photo
|
||||
and the workers were read. Results in `check/`.
|
||||
- Ready after 77 s. **Login page 200.**
|
||||
- **CSS 200** for all 3 of the page's own links, through wger-files (`text/css`; 2481 B, 277042 B and 1006 B).
|
||||
- Web login 302 with a session. `POST /api/v2/gallery/` with a 179 B PNG returned 201. **The photo read back through
|
||||
wger-files: 200, 179 B, `image/png`.**
|
||||
- Control: an unknown `/static/` file returned 404.
|
||||
- The container's env holds `WGER_USE_GUNICORN=True` and `WEB_CONCURRENCY=2`. Its log has **2 × „Booting worker"**.
|
||||
- anon peak 174,047,232 B = 166 MiB = 43.2 %. oom 0, oom_kill 0. restarts=0, oomkilled=false.
|
||||
- 20 parallel login GETs all returned 200.
|
||||
- This run is short. The 10-minute watch is run 1.
|
||||
|
||||
The night's figure (`night-burndown-2026-10-06/r762/`) was 157 MiB, 41 %, on the template without traefik's env. Today
|
||||
it is 166 to 170 MiB, 43 to 44 %, on the full catalog template.
|
||||
|
||||
## The ladder: why no step file was written
|
||||
The ladder records image moves. This change moves no image: `from` and `to` would both be
|
||||
`{wger: wger/server:2.7, wger-files: nginx:1.30.5-alpine}` (step key `10df849ded803b6e`). The writer handles
|
||||
from == to as a re-test, and `ladder.check_entry` refuses that entry:
|
||||
„a re-test (from == to) whose digest is the same as its digest_from tests nothing new — no new digest" (tool output,
|
||||
2026-10-08). `09` §5.4's render table says „deployed, pinned, catalog images equal → the catalog template — fixes flow".
|
||||
So a compose-only change reaches an installed wger at its next `up -d`, and the product's restart is `up -d`
|
||||
(`manager.go` ~L1411). It needs no ladder entry. No box runs wger (hub read, 07:58).
|
||||
|
||||
## Teardown
|
||||
- **Machine (bench 9401):** project `r762b` was taken down with `down -v`; afterwards 0 containers and 0 `r762`
|
||||
volumes. The harness ran its own `down -v`. `/root/r762b` and the helper scripts were deleted. `/opt/upg/templates/wger@gunicorn`
|
||||
was deleted. `/opt/upg`'s scripts and `templates/wger` were updated to the catalog's `32d1346` copies; they were
|
||||
older before. `/opt/upg/evidence/MV-wger` now holds today's run (the 10-06 run is kept in
|
||||
`audits/design-build-2026-10-06/F/bench/`). `/opt/upg/R762-gunicorn.log` stays. **9401 was stopped** (`pct status`:
|
||||
stopped), as it was found.
|
||||
- **Machine (9202):** only GETs and a dashboard login. 0 wger containers; `repo_url` is still the live catalog. Its
|
||||
deployed list was paperless-ngx, privatebin and recipe-importer at 08:0x CEST and paperless-ngx and privatebin at
|
||||
08:35. **This session did not touch recipe-importer**; something else removed it in that window.
|
||||
- **Host (demo-hp):** the `/tmp` copy files were deleted. Nothing else was created.
|
||||
- **Hub:** nothing.
|
||||
- No Docker command ran on DooPlex. Nothing was pruned.
|
||||
|
||||
The image's default admin password is redacted in every file here.
|
||||
@@ -0,0 +1,130 @@
|
||||
[06:05:43] scratch drive folders cleared before FROM (R-656): none existed
|
||||
[06:05:43] MV-wger: deploying wger at FROM {'wger': 'wger/server:2.7', 'wger-files': 'nginx:1.30.5-alpine'}
|
||||
[06:07:17] FROM settled=True in 93.0s :: {"wger": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}, "wger-files": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}}
|
||||
[06:07:17] fixture: the BOX walk's own (Wger), through upgrade_boxport
|
||||
[06:07:17] wger: the generated admin password does not log in (POST /en/user/login -> 200) — running the template's own after_install command (the app's CLI, as the product does after an install)
|
||||
[06:07:19] wger: after_install :: version 8.3.1, blocking by username FELHOM_AFTER_INSTALL_OK
|
||||
[06:07:21] wger: POST /api/v2/weightentry/ http=201
|
||||
[06:07:21] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:07:21] C1 (seed reads back BEFORE): True
|
||||
[06:07:21] MV-wger: swapping to TO {'wger': 'wger/server:2.7', 'wger-files': 'nginx:1.30.5-alpine'}
|
||||
[06:07:33] TO up -d rc=0
|
||||
[06:08:35] TO settled=True in 62.1s :: {"wger": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}, "wger-files": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}}
|
||||
[06:08:35] migration lines observed: 6
|
||||
[06:08:35] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:08:35] RESULT (seed reads back AFTER): True
|
||||
[06:08:36] memory watch: 600s, 4 callers on 1 path(s) at 172.18.0.2:8000
|
||||
[06:08:51] + 15s wger=288M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=292
|
||||
[06:09:06] + 30s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=580
|
||||
[06:09:21] + 46s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=872
|
||||
[06:09:37] + 61s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1160
|
||||
[06:09:52] + 76s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1452
|
||||
[06:10:07] + 91s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1740
|
||||
[06:10:22] + 106s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2028
|
||||
[06:10:37] + 121s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2320
|
||||
[06:10:52] + 136s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2608
|
||||
[06:11:07] + 152s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2896
|
||||
[06:11:23] + 167s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3188
|
||||
[06:11:38] + 182s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3476
|
||||
[06:11:53] + 197s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3768
|
||||
[06:12:08] + 212s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4056
|
||||
[06:12:23] + 227s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4344
|
||||
[06:12:38] + 242s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4636
|
||||
[06:12:54] + 258s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4924
|
||||
[06:13:09] + 273s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5216
|
||||
[06:13:24] + 288s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5504
|
||||
[06:13:39] + 303s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5792
|
||||
[06:13:54] + 318s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6084
|
||||
[06:14:09] + 334s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6372
|
||||
[06:14:25] + 349s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6664
|
||||
[06:14:40] + 364s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6952
|
||||
[06:14:55] + 379s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7240
|
||||
[06:15:10] + 394s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7529
|
||||
[06:15:25] + 409s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7820
|
||||
[06:15:40] + 424s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8108
|
||||
[06:15:55] + 440s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8400
|
||||
[06:16:11] + 455s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8688
|
||||
[06:16:26] + 470s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8978
|
||||
[06:16:41] + 485s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9268
|
||||
[06:16:56] + 500s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9556
|
||||
[06:17:11] + 515s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9848
|
||||
[06:17:26] + 530s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10136
|
||||
[06:17:42] + 546s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10428
|
||||
[06:17:57] + 561s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10716
|
||||
[06:18:12] + 576s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11008
|
||||
[06:18:27] + 591s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11296
|
||||
[06:18:42] + 606s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11584
|
||||
[06:18:42] memory watch: killed=False tight=[] requests=11584 codes={'302': 11584}
|
||||
[06:18:42] MV-wger: ABORT — putting the FROM images back
|
||||
[06:19:56] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:19:56] ABORT: app came back in 62.0s; data present=True
|
||||
{
|
||||
"harness_version": 5,
|
||||
"edge": "MV-wger",
|
||||
"app": "wger",
|
||||
"note": "definition step to wger@gunicorn",
|
||||
"from": {
|
||||
"wger": "wger/server:2.7",
|
||||
"wger-files": "nginx:1.30.5-alpine"
|
||||
},
|
||||
"to": {
|
||||
"wger": "wger/server:2.7",
|
||||
"wger-files": "nginx:1.30.5-alpine"
|
||||
},
|
||||
"verdict": "proven",
|
||||
"seed_read_before": true,
|
||||
"seed_read_after": true,
|
||||
"healthy_after": true,
|
||||
"migration_observed": "\u001b[2Kwger | Performing database migrations",
|
||||
"abort": "starts-and-serves",
|
||||
"abort_detail": null,
|
||||
"engine_state_after": null,
|
||||
"memory": {
|
||||
"soak_s": 606.5,
|
||||
"requested_s": 600,
|
||||
"requests": 11584,
|
||||
"codes": {
|
||||
"302": 11584
|
||||
},
|
||||
"first_kill": null,
|
||||
"containers": {
|
||||
"wger": {
|
||||
"limit": 402653184,
|
||||
"peak": 402653184,
|
||||
"peak_pct": 1.0,
|
||||
"anon_peak_sampled": 178151424,
|
||||
"anon_peak_pct": 0.442,
|
||||
"swap_peak": 0,
|
||||
"oom_kills": 0,
|
||||
"restarts": 0,
|
||||
"oomkilled_flag": false,
|
||||
"measured": true
|
||||
},
|
||||
"wger-files": {
|
||||
"limit": 33554432,
|
||||
"peak": 9281536,
|
||||
"peak_pct": 0.277,
|
||||
"anon_peak_sampled": 5308416,
|
||||
"anon_peak_pct": 0.158,
|
||||
"swap_peak": 0,
|
||||
"oom_kills": 0,
|
||||
"restarts": 0,
|
||||
"oomkilled_flag": false,
|
||||
"measured": true
|
||||
}
|
||||
},
|
||||
"unmeasured": [],
|
||||
"venue_swap_bytes": 0,
|
||||
"load": "reached"
|
||||
},
|
||||
"marks": [],
|
||||
"bench_overrides": null,
|
||||
"duration_s": 62.1,
|
||||
"measured_at": "2026-10-08T06:19:56Z",
|
||||
"evidence": "evidence/MV-wger",
|
||||
"scratch_cleared": [],
|
||||
"files_changed": [],
|
||||
"files_changed_detail": [],
|
||||
"files_ignored": [],
|
||||
"total_s": 853.6
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
174047232
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# R-762 bench check (2026-10-08): the gunicorn definition up on its own, then the login page, its CSS, a photo read back,
|
||||
# the workers in the container's own log, and the app's memory (anon). Project r762b only; down -v at the end.
|
||||
set -u
|
||||
W=/root/r762b; O=$W/out; rm -rf $W; mkdir -p $O; cd $W
|
||||
cp /opt/upg/templates/wger@gunicorn/docker-compose.yml docker-compose.yml
|
||||
umask 077
|
||||
printf 'DOMAIN=bench.invalid\nSUBDOMAIN=fitness\nSECRET_KEY=%s\n' "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" > .env
|
||||
docker compose -p r762b up -d > $O/up.txt 2>&1
|
||||
ID=$(docker inspect -f '{{.Id}}' wger); CG=$(find /sys/fs/cgroup -maxdepth 6 -type d -name "*$ID*" | head -1)
|
||||
touch $W/.sampling; ( max=0; while [ -f $W/.sampling ]; do a=$(awk '$1=="anon"{print $2}' $CG/memory.stat 2>/dev/null); [ -n "$a" ] && [ "$a" -gt "$max" ] && max=$a && echo $max > $O/anon-max; sleep 0.5; done ) &
|
||||
echo "cgroup: $CG" > $O/cg.txt
|
||||
IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' wger)
|
||||
FIP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' wger-files)
|
||||
t0=$(date +%s); for i in $(seq 1 300); do c=$(curl -s -o /dev/null -w '%{http_code}' http://$IP:8000/en/user/login); [ "$c" = 200 ] && break; sleep 1; done
|
||||
echo "ready_after_s=$(( $(date +%s)-t0 )) login=$c" > $O/checks.txt
|
||||
curl -s http://$IP:8000/en/user/login | grep -o '/static/[^"]*\.css' | head -3 | while read l; do echo "css $l via wger-files: $(curl -s -o /dev/null -w '%{http_code} %{size_download}B %{content_type}' http://$FIP$l)"; done >> $O/checks.txt
|
||||
# a photo through wger's own gallery API, signed in through its own login form as the image's seeded admin (bench-only
|
||||
# throwaway box; the password is not written anywhere). As a browser behind traefik: Host + X-Forwarded-Proto https, the
|
||||
# cookies carried by hand (Django's csrftoken is Secure; curl drops it over http).
|
||||
H=(-H "Host: fitness.bench.invalid" -H "X-Forwarded-Proto: https" -H "Origin: https://fitness.bench.invalid" -H "Referer: https://fitness.bench.invalid/en/user/login")
|
||||
curl -s -D $W/h1 -o $W/b1 "${H[@]}" http://$IP:8000/en/user/login
|
||||
CSRFC=$(grep -i '^set-cookie: csrftoken=' $W/h1 | sed 's/^[^=]*=//; s/;.*//' | tr -d '\r')
|
||||
FORM=$(grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' $W/b1 | head -1 | sed 's/.*value="//; s/"$//')
|
||||
curl -s -D $W/h2 -o /dev/null "${H[@]}" -H "Cookie: csrftoken=$CSRFC" --data-urlencode "csrfmiddlewaretoken=$FORM" --data-urlencode login=admin --data-urlencode password=<image-default, redacted> http://$IP:8000/en/user/login
|
||||
SID=$(grep -i '^set-cookie: sessionid=' $W/h2 | sed 's/^[^=]*=//; s/;.*//' | tr -d '\r')
|
||||
CSRF2=$(grep -i '^set-cookie: csrftoken=' $W/h2 | sed 's/^[^=]*=//; s/;.*//' | tr -d '\r'); [ -n "$CSRF2" ] && CSRFC=$CSRF2
|
||||
echo "web login: $(head -1 $W/h2 | tr -d '\r'), session cookie: $([ -n "$SID" ] && echo yes || echo no)" >> $O/checks.txt
|
||||
rm -f $W/h1 $W/h2 $W/b1
|
||||
python3 - > $W/p.png <<'PY'
|
||||
import struct, zlib, sys, os
|
||||
n=64; rgb=os.urandom(3); raw=b"".join(b"\x00"+rgb*n for _ in range(n))
|
||||
def ch(t,d): return struct.pack(">I",len(d))+t+d+struct.pack(">I",zlib.crc32(t+d)&0xffffffff)
|
||||
sys.stdout.buffer.write(b"\x89PNG\r\n\x1a\n"+ch(b"IHDR",struct.pack(">IIBBBBB",n,n,8,2,0,0,0))+ch(b"IDAT",zlib.compress(raw))+ch(b"IEND",b""))
|
||||
PY
|
||||
SZ=$(stat -c %s $W/p.png)
|
||||
R=$(curl -s -w '\n%{http_code}' "${H[@]}" -H "Cookie: csrftoken=$CSRFC; sessionid=$SID" -H "X-CSRFToken: $CSRFC" -F "image=@$W/p.png;type=image/png" -F date=2026-10-08 -F description=r762 http://$IP:8000/api/v2/gallery/)
|
||||
echo "POST /api/v2/gallery/ (${SZ} B PNG) -> $(echo "$R" | tail -1)" >> $O/checks.txt
|
||||
P=$(echo "$R" | head -n -1 | python3 -c 'import json,sys,re; print(re.sub(r"^https?://[^/]+","",json.load(sys.stdin).get("image","")))' 2>/dev/null)
|
||||
[ -z "$P" ] && echo "photo: NO PATH returned (the upload failed)" >> $O/checks.txt
|
||||
[ -n "$P" ] && echo "photo $P via wger-files: $(curl -s -o /dev/null -w '%{http_code} %{size_download}B %{content_type}' http://$FIP$P)" >> $O/checks.txt
|
||||
echo "control: an unknown /static/ file via wger-files: $(curl -s -o /dev/null -w '%{http_code}' http://$FIP/static/r762-nonexistent.css)" >> $O/checks.txt
|
||||
# light load: 20 login GETs, 10 parallel
|
||||
seq 1 20 | xargs -P 10 -I{} curl -s -o /dev/null -w 'conc %{http_code} %{time_total}\n' http://$IP:8000/en/user/login > $O/conc.txt
|
||||
sleep 3; rm -f $W/.sampling; sleep 1
|
||||
grep -E '^(oom|oom_kill) ' $CG/memory.events > $O/events.txt; cat $CG/memory.max > $O/memory.max; cat $CG/memory.peak > $O/memory.peak 2>/dev/null
|
||||
docker inspect -f 'restarts={{.RestartCount}} oomkilled={{.State.OOMKilled}} status={{.State.Status}} health={{.State.Health.Status}}' wger > $O/inspect.txt
|
||||
docker exec wger sh -c 'env | grep -E "^(WGER_USE_GUNICORN|WEB_CONCURRENCY)="' > $O/env-in-container.txt
|
||||
docker logs wger 2>&1 | sed 's/<image-default, redacted>/<image-default, redacted>/g' > $O/wger.log
|
||||
grep -E 'Using gunicorn|Using django|Booting worker|Listening at|Starting gunicorn|Starting development server' $O/wger.log > $O/server-lines.txt
|
||||
docker compose -p r762b down -v > $O/down.txt 2>&1
|
||||
rm -f $W/p.png .env
|
||||
echo done
|
||||
@@ -0,0 +1 @@
|
||||
cgroup: /sys/fs/cgroup/system.slice/docker-1dd3339a4eeb9ed63540b35d7ca09acca030259a68038e233e7270947dadcb05.scope
|
||||
@@ -0,0 +1,8 @@
|
||||
ready_after_s=77 login=200
|
||||
css /static/css/workout-manager.7007d84ce531.css via wger-files: 200 2481B text/css
|
||||
css /static/bootstrap-compiled.80a6279921f8.css via wger-files: 200 277042B text/css
|
||||
css /static/css/bootstrap-custom.400ad578123c.css via wger-files: 200 1006B text/css
|
||||
web login: HTTP/1.1 302 Found, session cookie: yes
|
||||
POST /api/v2/gallery/ (179 B PNG) -> 201
|
||||
photo /media/gallery/1/badf61de-7554-44b9-b72f-87df80fbd01d.png via wger-files: 200 179B image/png
|
||||
control: an unknown /static/ file via wger-files: 404
|
||||
@@ -0,0 +1,20 @@
|
||||
conc 200 0.044549
|
||||
conc 200 0.080295
|
||||
conc 200 0.119458
|
||||
conc 200 0.140526
|
||||
conc 200 0.147622
|
||||
conc 200 0.169720
|
||||
conc 200 0.175291
|
||||
conc 200 0.200793
|
||||
conc 200 0.203844
|
||||
conc 200 0.221900
|
||||
conc 200 0.196650
|
||||
conc 200 0.178437
|
||||
conc 200 0.147859
|
||||
conc 200 0.152214
|
||||
conc 200 0.146952
|
||||
conc 200 0.140927
|
||||
conc 200 0.146328
|
||||
conc 200 0.139658
|
||||
conc 200 0.140157
|
||||
conc 200 0.139606
|
||||
@@ -0,0 +1,14 @@
|
||||
Container wger-files Stopping
|
||||
Container wger-files Stopped
|
||||
Container wger-files Removing
|
||||
Container wger-files Removed
|
||||
Container wger Stopping
|
||||
Container wger Stopped
|
||||
Container wger Removing
|
||||
Container wger Removed
|
||||
Volume r762b_wger_media Removing
|
||||
Volume r762b_wger_data Removing
|
||||
Volume r762b_wger_static Removing
|
||||
Volume r762b_wger_media Removed
|
||||
Volume r762b_wger_static Removed
|
||||
Volume r762b_wger_data Removed
|
||||
@@ -0,0 +1,2 @@
|
||||
WGER_USE_GUNICORN=True
|
||||
WEB_CONCURRENCY=2
|
||||
@@ -0,0 +1,2 @@
|
||||
oom 0
|
||||
oom_kill 0
|
||||
@@ -0,0 +1 @@
|
||||
restarts=0 oomkilled=false status=running health=starting
|
||||
@@ -0,0 +1 @@
|
||||
402653184
|
||||
@@ -0,0 +1 @@
|
||||
402653184
|
||||
@@ -0,0 +1,5 @@
|
||||
Using gunicorn on port 8000...
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Starting gunicorn 26.1.0
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Listening at: http://0.0.0.0:8000 (28)
|
||||
[2026-10-08 08:32:13 +0200] [29] [INFO] Booting worker with pid: 29
|
||||
[2026-10-08 08:32:13 +0200] [30] [INFO] Booting worker with pid: 30
|
||||
@@ -0,0 +1,14 @@
|
||||
Volume "r762b_wger_static" Creating
|
||||
Volume "r762b_wger_static" Created
|
||||
Volume "r762b_wger_data" Creating
|
||||
Volume "r762b_wger_data" Created
|
||||
Volume "r762b_wger_media" Creating
|
||||
Volume "r762b_wger_media" Created
|
||||
Container wger Creating
|
||||
Container wger Created
|
||||
Container wger-files Creating
|
||||
Container wger-files Created
|
||||
Container wger Starting
|
||||
Container wger Started
|
||||
Container wger-files Starting
|
||||
Container wger-files Started
|
||||
@@ -0,0 +1,295 @@
|
||||
*** Using settings from env: settings.main
|
||||
level=INFO ts=2026-10-08 08:31:01,000 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
*** Database is empty or incomplete, setting it up now
|
||||
*** Using settings from env: settings.main
|
||||
Operations to perform:
|
||||
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
|
||||
Running migrations:
|
||||
Applying contenttypes.0001_initial... OK
|
||||
Applying auth.0001_initial... OK
|
||||
Applying account.0001_initial... OK
|
||||
Applying account.0002_email_max_length... OK
|
||||
Applying account.0003_alter_emailaddress_create_unique_verified_email... OK
|
||||
Applying account.0004_alter_emailaddress_drop_unique_email... OK
|
||||
Applying account.0005_emailaddress_idx_upper_email... OK
|
||||
Applying account.0006_emailaddress_lower... OK
|
||||
Applying account.0007_emailaddress_idx_email... OK
|
||||
Applying account.0008_emailaddress_unique_primary_email_fixup... OK
|
||||
Applying account.0009_emailaddress_unique_primary_email... OK
|
||||
Applying actstream.0001_initial... OK
|
||||
Applying actstream.0002_remove_action_data... OK
|
||||
Applying actstream.0003_add_follow_flag... OK
|
||||
Applying allauth_idp_oidc.0001_initial... OK
|
||||
Applying allauth_idp_oidc.0002_client_default_scopes... OK
|
||||
Applying allauth_idp_oidc.0003_client_allow_uri_wildcards... OK
|
||||
Applying contenttypes.0002_remove_content_type_name... OK
|
||||
Applying auth.0002_alter_permission_name_max_length... OK
|
||||
Applying auth.0003_alter_user_email_max_length... OK
|
||||
Applying auth.0004_alter_user_username_opts... OK
|
||||
Applying auth.0005_alter_user_last_login_null... OK
|
||||
Applying auth.0006_require_contenttypes_0002... OK
|
||||
Applying auth.0007_alter_validators_add_error_messages... OK
|
||||
Applying auth.0008_alter_user_username_max_length... OK
|
||||
Applying auth.0009_alter_user_last_name_max_length... OK
|
||||
Applying auth.0010_alter_group_name_max_length... OK
|
||||
Applying auth.0011_update_proxy_permissions... OK
|
||||
Applying auth.0012_alter_user_first_name_max_length... OK
|
||||
Applying authtoken.0001_initial... OK
|
||||
Applying authtoken.0002_auto_20160226_1747... OK
|
||||
Applying authtoken.0003_tokenproxy... OK
|
||||
Applying authtoken.0004_alter_tokenproxy_options... OK
|
||||
Applying axes.0001_initial... OK
|
||||
Applying axes.0002_auto_20151217_2044... OK
|
||||
Applying axes.0003_auto_20160322_0929... OK
|
||||
Applying axes.0004_auto_20181024_1538... OK
|
||||
Applying axes.0005_remove_accessattempt_trusted... OK
|
||||
Applying axes.0006_remove_accesslog_trusted... OK
|
||||
Applying axes.0007_alter_accessattempt_unique_together... OK
|
||||
Applying axes.0008_accessfailurelog... OK
|
||||
Applying axes.0009_add_session_hash... OK
|
||||
Applying axes.0010_accessattemptexpiration... OK
|
||||
Applying gym.0001_initial... OK
|
||||
Applying core.0001_initial... OK
|
||||
Applying config.0001_initial... OK
|
||||
Applying config.0002_auto_20190618_1617... OK
|
||||
Applying config.0003_delete_languageconfig... OK
|
||||
Applying sessions.0001_initial... OK
|
||||
Applying weight.0001_initial... OK
|
||||
Applying weight.0002_auto_20150604_2139... OK
|
||||
Applying weight.0003_auto_20160416_1030... OK
|
||||
Applying weight.0004_multiple_weight_entries_per_day... OK
|
||||
Applying weight.0005_add_uuid... OK
|
||||
Applying nutrition.0001_initial... OK
|
||||
Applying nutrition.0002_auto_20170101_1538... OK
|
||||
Applying nutrition.0003_auto_20170118_2308... OK
|
||||
Applying nutrition.0004_auto_20200819_2310... OK
|
||||
Applying nutrition.0005_logitem... OK
|
||||
Applying nutrition.0006_auto_20201201_0653... OK
|
||||
Applying nutrition.0007_auto_20201214_0013... OK
|
||||
Applying nutrition.0008_auto_20210102_1446... OK
|
||||
Applying nutrition.0009_meal_name... OK
|
||||
Applying nutrition.0010_logitem_meal... OK
|
||||
Applying nutrition.0011_alter_logitem_datetime... OK
|
||||
Applying nutrition.0012_alter_ingredient_license_author... OK
|
||||
Applying gym.0002_auto_20151003_1944... OK
|
||||
Applying gym.0003_auto_20151003_2008... OK
|
||||
Applying gym.0004_auto_20151003_2357... OK
|
||||
Applying gym.0005_auto_20151023_1522... OK
|
||||
Applying gym.0006_auto_20160214_1013... OK
|
||||
Applying gym.0007_auto_20170123_0920... OK
|
||||
Applying gym.0008_auto_20190618_1617... OK
|
||||
Applying exercises.0001_initial... OK
|
||||
Applying manager.0001_initial... OK
|
||||
Applying manager.0002_auto_20150202_2040... OK
|
||||
Applying manager.0004_auto_20150609_1603... OK
|
||||
Applying core.0002_auto_20141225_1512... OK
|
||||
Applying core.0003_auto_20150217_1554... OK
|
||||
Applying core.0004_auto_20150217_1914... OK
|
||||
Applying core.0005_auto_20151025_2236... OK
|
||||
Applying core.0006_auto_20151025_2237... OK
|
||||
Applying core.0007_repetitionunit... OK
|
||||
Applying core.0008_weightunit... OK
|
||||
Applying core.0009_auto_20160303_2340... OK
|
||||
Applying core.0010_auto_20170403_0144... OK
|
||||
Applying core.0011_auto_20201201_0653... OK
|
||||
Applying core.0012_auto_20210210_1228... OK
|
||||
Applying core.0013_auto_20210726_1729... OK
|
||||
Applying nutrition.0013_ingredient_image... OK
|
||||
Applying nutrition.0014_license_information... OK
|
||||
Applying nutrition.0015_alter_ingredient_creation_date_and_more... OK
|
||||
Applying nutrition.0016_alter_logitem_options_and_more... OK
|
||||
Applying nutrition.0017_remove_nutritionplan_language_alter_logitem_meal... OK
|
||||
Applying nutrition.0018_nutritionplan_goal_carbs_nutritionplan_goal_energy_and_more... OK
|
||||
Applying nutrition.0019_alter_image_license_author_and_more... OK
|
||||
Applying nutrition.0020_full_text_search... OK
|
||||
Applying nutrition.0021_add_fibers_field... OK
|
||||
Applying nutrition.0022_add_remote_id_increase_author_field_length... OK
|
||||
Applying nutrition.0023_fiber_spelling... OK
|
||||
Applying nutrition.0024_remove_ingredient_status... OK
|
||||
Applying nutrition.0025_add_last_image_check... OK
|
||||
Applying nutrition.0026_add_start_and_end_fields... OK
|
||||
Applying nutrition.0027_prefill_end_date... OK
|
||||
Applying nutrition.0028_ingredient_dietary_properties... OK
|
||||
Applying nutrition.0029_ingredient_nutriscore... OK
|
||||
Applying manager.0005_auto_20160303_2008... OK
|
||||
Applying manager.0006_auto_20160303_2138... OK
|
||||
Applying manager.0007_auto_20160311_2258... OK
|
||||
Applying manager.0008_auto_20190618_1617... OK
|
||||
Applying manager.0009_auto_20201202_1559... OK
|
||||
Applying manager.0010_auto_20210102_1446... OK
|
||||
Applying manager.0011_remove_set_exercises... OK
|
||||
Applying manager.0012_auto_20210430_1449... OK
|
||||
Applying manager.0013_set_comment... OK
|
||||
Applying manager.0014_auto_20210717_1858... OK
|
||||
Applying manager.0015_auto_20211028_1113... OK
|
||||
Applying exercises.0002_auto_20150307_1841... OK
|
||||
Applying exercises.0003_auto_20160921_2000... OK
|
||||
Applying exercises.0004_auto_20170404_0114... OK
|
||||
Applying exercises.0005_auto_20190618_1617... OK
|
||||
Applying exercises.0006_auto_20201203_0203... OK
|
||||
Applying exercises.0007_auto_20201203_1042... OK
|
||||
Applying exercises.0008_exercisebase... OK
|
||||
Applying exercises.0009_auto_20201211_0139... OK
|
||||
Applying exercises.0010_auto_20201211_0205... OK
|
||||
Applying exercises.0011_auto_20201214_0033... OK
|
||||
Applying exercises.0012_auto_20210327_1219... OK
|
||||
Applying exercises.0013_auto_20210503_1232... OK
|
||||
Applying exercises.0014_exerciseimage_style... OK
|
||||
Applying exercises.0015_exercise_videos... OK
|
||||
Applying exercises.0016_exercisealias... OK
|
||||
Applying exercises.0017_muscle_name_en... OK
|
||||
Applying core.0013_userprofile_email_verified... OK
|
||||
Applying core.0014_merge_20210818_1735... OK
|
||||
Applying exercises.0018_delete_pending_exercises... OK
|
||||
Applying exercises.0019_exercise_crowdsourcing_changes... OK
|
||||
Applying manager.0016_move_to_exercise_base... OK
|
||||
Applying manager.0017_alter_workoutlog_exercise_base... OK
|
||||
Applying exercises.0020_historicalexerciseimage_historicalexercisevideo... OK
|
||||
Applying exercises.0021_deletionlog... OK
|
||||
Applying exercises.0022_alter_exercise_license_author_and_more... OK
|
||||
Applying exercises.0023_make_uuid_unique... OK
|
||||
Applying exercises.0024_license_information... OK
|
||||
Applying exercises.0025_rename_update_date_exercise_last_update_and_more... OK
|
||||
Applying exercises.0026_deletionlog_replaced_by... OK
|
||||
Applying exercises.0027_alter_deletionlog_replaced_by_and_more... OK
|
||||
Applying exercises.0028_add_uuid_alias_and_comments... OK
|
||||
Applying exercises.0029_full_text_search... OK
|
||||
Applying exercises.0030_increase_author_field_length... OK
|
||||
Applying exercises.0032_rename_exercise... OK
|
||||
Applying core.0015_alter_language_short_name... OK
|
||||
Applying core.0016_alter_language_short_name... OK
|
||||
Applying manager.0018_flexible_routines... OK
|
||||
Applying manager.0019_flexible_routines_migration... OK
|
||||
Applying manager.0021_flexible_routines_cleanup... OK
|
||||
Applying core.0017_language_full_name_en... OK
|
||||
Applying core.0018_rounding... OK
|
||||
Applying core.0019_delete_daysofweek... OK
|
||||
Applying core.0020_add_trophies_enabled_to_userprofile... OK
|
||||
Applying core.0021_add_unit_type_to_repetitionunit... OK
|
||||
Applying nutrition.0030_add_indices... OK
|
||||
Applying nutrition.0031_start_weight_unit_merge... OK
|
||||
Applying nutrition.0032_continue_weight_unit_merge... OK
|
||||
Applying nutrition.0033_finalize_weight_unit_merge... OK
|
||||
Applying nutrition.0034_ingredient_trigram_gin_index... OK
|
||||
Applying nutrition.0035_add_uuids... OK
|
||||
Applying nutrition.0036_alter_image_license_author_and_more... OK
|
||||
Applying nutrition.0037_powersync_synced_ingredient_tables... OK
|
||||
Applying measurements.0001_initial... OK
|
||||
Applying measurements.0002_auto_20210722_1042... OK
|
||||
Applying measurements.0003_alter_measurement_unique_together_and_more... OK
|
||||
Applying measurements.0004_add_uuids... OK
|
||||
Applying measurements.0005_alter_measurement_date... OK
|
||||
Applying trophies.0001_initial... OK
|
||||
Applying trophies.0002_load_initial_trophies... OK
|
||||
Applying manager.0022_alter_rir_type... OK
|
||||
Applying manager.0023_change_validators... OK
|
||||
Applying manager.0024_log_and_session_uuid... OK
|
||||
Applying manager.0025_change_pk_to_uuid... OK
|
||||
Applying trophies.0003_migrate_context_data_uuids... OK
|
||||
Applying manager.0026_change_pk_to_uuid_swap... OK
|
||||
Applying core.0022_move_email_verified_to_emailaddress... OK
|
||||
Applying core.0023_create_publication... OK
|
||||
Applying manager.0027_cleanup_fields... OK
|
||||
Applying manager.0028_backfill_session_day... OK
|
||||
Applying gallery.0001_initial... OK
|
||||
Applying exercises.0033_uniqueness_constraint_translations... OK
|
||||
Applying exercises.0034_add_exercise_image_dimensions... OK
|
||||
Applying exercises.0035_add_is_ai_generated... OK
|
||||
Applying exercises.0036_add_markdown_description_field... OK
|
||||
Applying exercises.0037_replace_variation_with_uuid_field... OK
|
||||
Applying exercises.0038_sync_model_changes... OK
|
||||
Applying exercises.0039_translation_alias_trigram_gin_index... OK
|
||||
Applying exercises.0040_alter_exercise_license_author_and_more... OK
|
||||
Applying core.0024_backfill_emailaddress... OK
|
||||
Applying core.0025_remove_unused_fields_in_userprofile... OK
|
||||
Applying core.0026_alter_userprofile_birthdate_alter_userprofile_height... OK
|
||||
Applying core.0027_powersync_publication... OK
|
||||
Applying core.0028_longlivedsession... OK
|
||||
Applying core.0029_userprofile_timezone... OK
|
||||
Applying easy_thumbnails.0001_initial... OK
|
||||
Applying easy_thumbnails.0002_thumbnaildimensions... OK
|
||||
Applying mailer.0001_initial... OK
|
||||
Applying mailer.0002_auto_20190618_1617... OK
|
||||
Applying mailer.0003_auto_20201201_0653... OK
|
||||
Applying manager.0029_alter_workoutsession_options_and_more... OK
|
||||
Applying measurements.0006_health_sync... OK
|
||||
Applying measurements.0007_migrate_weight... OK
|
||||
Applying measurements.0008_dynamic_type... OK
|
||||
Applying mfa.0001_initial... OK
|
||||
Applying mfa.0002_authenticator_timestamps... OK
|
||||
Applying mfa.0003_authenticator_type_uniq... OK
|
||||
Applying sites.0001_initial... OK
|
||||
Applying sites.0002_alter_domain_unique... OK
|
||||
Applying socialaccount.0001_initial... OK
|
||||
Applying socialaccount.0002_token_max_lengths... OK
|
||||
Applying socialaccount.0003_extra_data_default_dict... OK
|
||||
Applying socialaccount.0004_app_provider_id_settings... OK
|
||||
Applying socialaccount.0005_socialtoken_nullable_app... OK
|
||||
Applying socialaccount.0006_alter_socialaccount_extra_data... OK
|
||||
Applying token_blacklist.0001_initial... OK
|
||||
Applying token_blacklist.0002_outstandingtoken_jti_hex... OK
|
||||
Applying token_blacklist.0003_auto_20171017_2007... OK
|
||||
Applying token_blacklist.0004_auto_20171017_2013... OK
|
||||
Applying token_blacklist.0005_remove_outstandingtoken_jti... OK
|
||||
Applying token_blacklist.0006_auto_20171017_2113... OK
|
||||
Applying token_blacklist.0007_auto_20171017_2214... OK
|
||||
Applying token_blacklist.0008_migrate_to_bigautofield... OK
|
||||
Applying token_blacklist.0010_fix_migrate_to_bigautofield... OK
|
||||
Applying token_blacklist.0011_linearizes_history... OK
|
||||
Applying token_blacklist.0012_alter_outstandingtoken_user... OK
|
||||
Applying token_blacklist.0013_alter_blacklistedtoken_options_and_more... OK
|
||||
Applying weight.0006_delete_weightentry... OK
|
||||
*** Using settings from env: settings.main
|
||||
Installed 1 object(s) from 1 fixture(s)
|
||||
Installed 33 object(s) from 1 fixture(s)
|
||||
Installed 7 object(s) from 1 fixture(s)
|
||||
Installed 3 object(s) from 1 fixture(s)
|
||||
Installed 5 object(s) from 1 fixture(s)
|
||||
Installed 8 object(s) from 1 fixture(s)
|
||||
Installed 6 object(s) from 1 fixture(s)
|
||||
Installed 1 object(s) from 1 fixture(s)
|
||||
Installed 12 object(s) from 1 fixture(s)
|
||||
Installed 16 object(s) from 1 fixture(s)
|
||||
Installed 8 object(s) from 1 fixture(s)
|
||||
Installed 872 object(s) from 1 fixture(s)
|
||||
Installed 2429 object(s) from 1 fixture(s)
|
||||
Installed 1 object(s) from 1 fixture(s)
|
||||
Installed 1 object(s) from 1 fixture(s)
|
||||
Installed 1 object(s) from 1 fixture(s)
|
||||
*** Using settings from env: settings.main
|
||||
*** Password for user admin was reset to '<image-default, redacted>'
|
||||
Installed 3 object(s) from 1 fixture(s)
|
||||
Running in production mode, running collectstatic now
|
||||
level=INFO ts=2026-10-08 08:31:51,700 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
|
||||
11362 static files copied to '/home/wger/static', 11362 post-processed.
|
||||
Performing database migrations
|
||||
level=INFO ts=2026-10-08 08:32:06,548 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
System check identified some issues:
|
||||
|
||||
WARNINGS:
|
||||
?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
Operations to perform:
|
||||
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
|
||||
Running migrations:
|
||||
No migrations to apply.
|
||||
Your models in app(s): 'exercises', 'gallery' have changes that are not yet reflected in a migration, and so won't be applied.
|
||||
Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
|
||||
level=INFO ts=2026-10-08 08:32:09,710 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
System check identified some issues:
|
||||
|
||||
WARNINGS:
|
||||
?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
Set site URL to fitness.bench.invalid
|
||||
Using gunicorn on port 8000...
|
||||
level=INFO ts=2026-10-08 08:32:12,670 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Starting gunicorn 26.1.0
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Listening at: http://0.0.0.0:8000 (28)
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Using worker: sync
|
||||
[2026-10-08 08:32:13 +0200] [29] [INFO] Booting worker with pid: 29
|
||||
[2026-10-08 08:32:13 +0200] [30] [INFO] Booting worker with pid: 30
|
||||
[2026-10-08 08:32:13 +0200] [28] [INFO] Control socket listening at /home/wger/.gunicorn/gunicorn.ctl
|
||||
level=INFO ts=2026-10-08 08:32:14,549 module=database path=/home/wger/.local/lib/python3.12/site-packages/axes/handlers/database.py line=295 message=AXES: Successful login by {username: "********************", ip_address: "********************", user_agent: "curl/8.14.1", path_info: "/en/user/login"}.
|
||||
level=INFO ts=2026-10-08 08:32:14,551 module=database path=/home/wger/.local/lib/python3.12/site-packages/axes/handlers/database.py line=425 message=AXES: Cleaned up 0 expired access attempts from database that were older than 2026-10-08 06:27:14.291696+00:00
|
||||
@@ -0,0 +1,159 @@
|
||||
# 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.7
|
||||
container_name: wger
|
||||
# R-737 (2026-09-30): wger's app login API (the mobile app's) signs JWTs with JWT_PRIVATE_KEY / JWT_PUBLIC_KEY — an
|
||||
# RSA pair the deploy's generators cannot make, and without it a CORRECT password answered 500. So the pair is made
|
||||
# ONCE by wger's own `manage.py generate-jwt-keys`, kept 0600 on wger's own data volume (a restore brings the same
|
||||
# key back), and loaded before the image's own entrypoint. Never printed.
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
K=/home/wger/db/.felhom-jwt.env
|
||||
if [ ! -s "$$K" ]; then
|
||||
(cd /home/wger/src && python3 manage.py generate-jwt-keys 2>/dev/null) | grep -E '^JWT_(PRIVATE|PUBLIC)_KEY=' > "$$K.tmp"
|
||||
if [ "$$(grep -c . "$$K.tmp")" = 2 ]; then mv "$$K.tmp" "$$K" && chmod 600 "$$K"; else rm -f "$$K.tmp"; echo "felhom: JWT keys could not be made" >&2; fi
|
||||
fi
|
||||
if [ -s "$$K" ]; then set -a; . "$$K"; set +a; fi
|
||||
exec /home/wger/entrypoint.sh
|
||||
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
|
||||
# R-738: the image runs `manage.py migrate` at start ONLY with this switch (entrypoint.sh). Without it an update
|
||||
# that brings migrations leaves wger serving its front page over an unmigrated database (login 500).
|
||||
- DJANGO_PERFORM_MIGRATIONS=True
|
||||
# R-752 (decided by CC unattended 2026-10-01, `09` §3 decision 58 — operator may reverse): django-axes locked by
|
||||
# ip_address, and behind the tunnel every visitor has the tunnel's address (R-753) — a stranger's 10 wrong tries
|
||||
# locked out EVERY household member for 30 min. Now only the targeted name, for 5 min (each try during a lock
|
||||
# restarts it — wger 2.7 hard-codes that — so short is kinder), counted in the database (the default cache
|
||||
# handler warns axes.W001). Measured on 9202: the other member unaffected; the targeted one in again at 7.5 min.
|
||||
- AXES_LOCKOUT_PARAMETERS=username
|
||||
- AXES_COOLOFF_TIME=5
|
||||
- AXES_HANDLER=axes.handlers.database.AxesDatabaseHandler
|
||||
# R-763 (2026-10-05): the image defaults both to True. After the install the admin exists (after_install sets its
|
||||
# password), so nobody needs wger's own sign-up: a stranger could make an account through the front door, and every
|
||||
# anonymous visit to the dashboard made a guest user row (wger's middleware create_temporary_user). Both read by
|
||||
# settings/main.py as env.bool (wger 2.7 L179-180); the sign-up view then redirects to the features page.
|
||||
- ALLOW_REGISTRATION=False
|
||||
- ALLOW_GUEST_USERS=False
|
||||
# R-764 (2026-10-05): mail through the box's relay (smtp_mapping in .felhom.yml, tls_mode plaintext -> :2526).
|
||||
# wger 2.7 settings/main.py:162 reads the EMAIL_* group ONLY when ENABLE_EMAIL is true, and then env.str() with
|
||||
# no default on EMAIL_HOST_USER / EMAIL_HOST_PASSWORD — so both stay defined-EMPTY here (the relay takes no
|
||||
# login; an absent one would stop wger at start). ENABLE_EMAIL is the gate: False unless the mail toggle injects
|
||||
# "True". EMAIL_USE_TLS False: Django's STARTTLS verifies the certificate and the relay's is self-signed.
|
||||
- ENABLE_EMAIL=${ENABLE_EMAIL:-False}
|
||||
- EMAIL_HOST=${EMAIL_HOST:-}
|
||||
- EMAIL_PORT=${EMAIL_PORT:-2526}
|
||||
- EMAIL_HOST_USER=
|
||||
- EMAIL_HOST_PASSWORD=
|
||||
- EMAIL_USE_TLS=False
|
||||
- EMAIL_USE_SSL=False
|
||||
- FROM_EMAIL=${FROM_EMAIL:-wger Workout Manager <wger@example.com>}
|
||||
- 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}
|
||||
# R-762 (2026-10-06): production mode, as upstream's own prod.env (wger-project/docker config/prod.env). With
|
||||
# DJANGO_DEBUG=False the image's entrypoint runs `collectstatic` at every start (entrypoint.sh:27) into
|
||||
# /home/wger/static, and Django stops serving /static and /media itself (it never did in production — upstream
|
||||
# puts nginx in front). wger-files below serves both from the shared volumes.
|
||||
- DJANGO_DEBUG=False
|
||||
# R-762 (2026-10-08): the real web server. The image's entrypoint.sh runs `gunicorn wger.wsgi:application --preload
|
||||
# --bind 0.0.0.0:$PORT` when WGER_USE_GUNICORN is "True" (else Django's development server, `manage.py runserver`).
|
||||
# It passes no -w and the image has no gunicorn.conf.py, so the worker count is gunicorn's own WEB_CONCURRENCY
|
||||
# (unset = 1). Two workers: measured on the bench and on 9202 (2 x "Booting worker" in the log), anon peak well
|
||||
# under the 384M limit — with --preload the workers share the app's pages with the master.
|
||||
- WGER_USE_GUNICORN=True
|
||||
- WEB_CONCURRENCY=2
|
||||
volumes:
|
||||
- wger_data:/home/wger/db
|
||||
- wger_media:/home/wger/media
|
||||
- wger_static:/home/wger/static
|
||||
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"
|
||||
|
||||
# R-762 (2026-10-06): the file server upstream's production compose puts in front of wger (its `nginx` service and
|
||||
# config/nginx.conf: `location /static/ { alias /wger/static/; }`, `location /media/ { alias /wger/media/; }`).
|
||||
# Here traefik is already the front door, so traefik sends ONLY /static/ and /media/ to this nginx and everything
|
||||
# else to wger as before — no config file is needed: nginx's stock config serves /usr/share/nginx/html, and the two
|
||||
# volumes are mounted there read-only. Every gate the box puts in front of the app wraps EVERY router of the stack
|
||||
# (stacks/setup_gate.go, family_gate.go), so this router is gated exactly like wger's own.
|
||||
wger-files:
|
||||
image: nginx:1.30.5-alpine
|
||||
container_name: wger-files
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- wger
|
||||
volumes:
|
||||
- wger_static:/usr/share/nginx/html/static:ro
|
||||
- wger_media:/usr/share/nginx/html/media:ro
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 32M
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.wger-files.rule=Host(`${SUBDOMAIN}.${DOMAIN}`) && (PathPrefix(`/static/`) || PathPrefix(`/media/`))"
|
||||
- "traefik.http.routers.wger-files.entrypoints=websecure"
|
||||
- "traefik.http.routers.wger-files.tls=true"
|
||||
- "traefik.http.routers.wger-files.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.wger-files.loadbalancer.server.port=80"
|
||||
|
||||
volumes:
|
||||
wger_data:
|
||||
wger_media:
|
||||
wger_static:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"wger": {
|
||||
"status": "running",
|
||||
"health": "healthy",
|
||||
"restarts": 0,
|
||||
"exit": 0
|
||||
},
|
||||
"wger-files": {
|
||||
"status": "running",
|
||||
"health": "healthy",
|
||||
"restarts": 0,
|
||||
"exit": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
[2Kwger-files | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
|
||||
[2Kwger-files | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
|
||||
[2Kwger-files | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
|
||||
[2Kwger-files | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
|
||||
[2Kwger-files | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Configuration complete; ready for start up
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: using the "epoll" event method
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: nginx/1.30.5
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: built by gcc 15.2.0 (Alpine 15.2.0)
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: OS: Linux 7.0.14-20-pve
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 524288:524288
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker processes
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 30
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 31
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 32
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 33
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 34
|
||||
[2Kwger-files | 2026/10/08 06:18:54 [notice] 1#1: start worker process 35
|
||||
[2Kwger-files | 127.0.0.1 - - [08/Oct/2026:06:19:24 +0000] "GET / HTTP/1.1" 200 896 "-" "Wget" "-"
|
||||
[2K[2Kwger-files | 127.0.0.1 - - [08/Oct/2026:06:19:54 +0000] "GET / HTTP/1.1" 200 896 "-" "Wget" "-"
|
||||
wger | *** Using settings from env: settings.main
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:18:55,562 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | Running in production mode, running collectstatic now
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:18:57,102 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger |
|
||||
[2Kwger | 22725 static files deleted, 11362 static files copied to '/home/wger/static', 11362 post-processed.
|
||||
[2Kwger | Performing database migrations
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:23,909 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | System check identified some issues:
|
||||
[2Kwger |
|
||||
[2Kwger | WARNINGS:
|
||||
[2Kwger | ?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
[2Kwger | HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
[2Kwger | Operations to perform:
|
||||
[2Kwger | 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
|
||||
[2Kwger | Running migrations:
|
||||
[2Kwger | No migrations to apply.
|
||||
[2Kwger | Your models in app(s): 'exercises', 'gallery' have changes that are not yet reflected in a migration, and so won't be applied.
|
||||
[2Kwger | Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:27,332 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | System check identified some issues:
|
||||
[2Kwger |
|
||||
[2Kwger | WARNINGS:
|
||||
[2Kwger | ?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
[2Kwger | HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
[2Kwger | Set site URL to fitness.gate.invalid
|
||||
[2Kwger | Using django's development server on port 8000...
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:29,776 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:31,051 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:31,063 module=autoreload path=/home/wger/.local/lib/python3.12/site-packages/django/utils/autoreload.py line=681 message=Watching for file changes with StatReloader
|
||||
[2Kwger | Performing system checks...
|
||||
[2Kwger |
|
||||
[2Kwger | System check identified some issues:
|
||||
[2Kwger |
|
||||
[2Kwger | WARNINGS:
|
||||
[2Kwger | ?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
[2Kwger | HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
[2Kwger |
|
||||
[2Kwger | System check identified 1 issue (0 silenced).
|
||||
[2Kwger | October 08, 2026 - 08:19:32
|
||||
[2Kwger | Django version 6.0.8, using settings 'settings.main'
|
||||
[2Kwger | Starting development server at http://0.0.0.0:8000/
|
||||
[2Kwger | Quit the server with CONTROL-C.
|
||||
[2Kwger |
|
||||
[2Kwger | WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
|
||||
[2Kwger | For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/
|
||||
[2Kwger | [08/Oct/2026 08:19:54] "HEAD / HTTP/1.1" 302 0
|
||||
[2Kwger | [08/Oct/2026 08:19:54] "HEAD /en/ HTTP/1.1" 302 0
|
||||
[2Kwger | [08/Oct/2026 08:19:54] "HEAD /en/software/features HTTP/1.1" 200 0
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "GET /en/user/login HTTP/1.1" 200 43827
|
||||
[2Kwger | level=WARNING ts=2026-10-08 08:19:56,238 module=log path=/home/wger/.local/lib/python3.12/site-packages/django/utils/log.py line=249 message=Forbidden: /api/v2/weightentry/
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "GET /api/v2/weightentry/?weight=105.51 HTTP/1.1" 403 58
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "GET /en/user/login HTTP/1.1" 200 43827
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:56,495 module=database path=/home/wger/.local/lib/python3.12/site-packages/axes/handlers/database.py line=295 message=AXES: Successful login by {username: "********************", ip_address: "********************", user_agent: "curl/8.14.1", path_info: "/en/user/login"}.
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:19:56,504 module=database path=/home/wger/.local/lib/python3.12/site-packages/axes/handlers/database.py line=425 message=AXES: Cleaned up 1 expired access attempts from database that were older than 2026-10-08 06:14:56.348758+00:00
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "POST /en/user/login HTTP/1.1" 302 0
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "GET /api/v2/weightentry/?weight=199.99 HTTP/1.1" 200 52
|
||||
[2Kwger | [08/Oct/2026 08:19:56] "GET /api/v2/weightentry/?weight=105.51 HTTP/1.1" 200 159
|
||||
@@ -0,0 +1 @@
|
||||
null
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
[2Kwger | Performing database migrations
|
||||
[2Kwger | 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
|
||||
[2Kwger | Running migrations:
|
||||
[2Kwger | No migrations to apply.
|
||||
[2Kwger | Your models in app(s): 'exercises', 'gallery' have changes that are not yet reflected in a migration, and so won't be applied.
|
||||
[2Kwger | Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
|
||||
@@ -0,0 +1,60 @@
|
||||
[06:05:43] scratch drive folders cleared before FROM (R-656): none existed
|
||||
[06:05:43] MV-wger: deploying wger at FROM {'wger': 'wger/server:2.7', 'wger-files': 'nginx:1.30.5-alpine'}
|
||||
[06:07:17] FROM settled=True in 93.0s :: {"wger": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}, "wger-files": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}}
|
||||
[06:07:17] fixture: the BOX walk's own (Wger), through upgrade_boxport
|
||||
[06:07:17] wger: the generated admin password does not log in (POST /en/user/login -> 200) — running the template's own after_install command (the app's CLI, as the product does after an install)
|
||||
[06:07:19] wger: after_install :: version 8.3.1, blocking by username FELHOM_AFTER_INSTALL_OK
|
||||
[06:07:21] wger: POST /api/v2/weightentry/ http=201
|
||||
[06:07:21] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:07:21] C1 (seed reads back BEFORE): True
|
||||
[06:07:21] MV-wger: swapping to TO {'wger': 'wger/server:2.7', 'wger-files': 'nginx:1.30.5-alpine'}
|
||||
[06:07:33] TO up -d rc=0
|
||||
[06:08:35] TO settled=True in 62.1s :: {"wger": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}, "wger-files": {"status": "running", "health": "healthy", "restarts": 0, "exit": 0}}
|
||||
[06:08:35] migration lines observed: 6
|
||||
[06:08:35] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:08:35] RESULT (seed reads back AFTER): True
|
||||
[06:08:36] memory watch: 600s, 4 callers on 1 path(s) at 172.18.0.2:8000
|
||||
[06:08:51] + 15s wger=288M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=292
|
||||
[06:09:06] + 30s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=580
|
||||
[06:09:21] + 46s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=872
|
||||
[06:09:37] + 61s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1160
|
||||
[06:09:52] + 76s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1452
|
||||
[06:10:07] + 91s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=1740
|
||||
[06:10:22] + 106s wger=291M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2028
|
||||
[06:10:37] + 121s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2320
|
||||
[06:10:52] + 136s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2608
|
||||
[06:11:07] + 152s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=2896
|
||||
[06:11:23] + 167s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3188
|
||||
[06:11:38] + 182s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3476
|
||||
[06:11:53] + 197s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=3768
|
||||
[06:12:08] + 212s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4056
|
||||
[06:12:23] + 227s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4344
|
||||
[06:12:38] + 242s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4636
|
||||
[06:12:54] + 258s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=4924
|
||||
[06:13:09] + 273s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5216
|
||||
[06:13:24] + 288s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5504
|
||||
[06:13:39] + 303s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=5792
|
||||
[06:13:54] + 318s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6084
|
||||
[06:14:09] + 334s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6372
|
||||
[06:14:25] + 349s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6664
|
||||
[06:14:40] + 364s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=6952
|
||||
[06:14:55] + 379s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7240
|
||||
[06:15:10] + 394s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7529
|
||||
[06:15:25] + 409s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=7820
|
||||
[06:15:40] + 424s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8108
|
||||
[06:15:55] + 440s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8400
|
||||
[06:16:11] + 455s wger=292M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8688
|
||||
[06:16:26] + 470s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=8978
|
||||
[06:16:41] + 485s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9268
|
||||
[06:16:56] + 500s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9556
|
||||
[06:17:11] + 515s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=9848
|
||||
[06:17:26] + 530s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10136
|
||||
[06:17:42] + 546s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10428
|
||||
[06:17:57] + 561s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=10716
|
||||
[06:18:12] + 576s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11008
|
||||
[06:18:27] + 591s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11296
|
||||
[06:18:42] + 606s wger=293M/384M peak=384M kills=0 rs=0 wger-files=6M/32M peak=8M kills=0 rs=0 reqs=11584
|
||||
[06:18:42] memory watch: killed=False tight=[] requests=11584 codes={'302': 11584}
|
||||
[06:18:42] MV-wger: ABORT — putting the FROM images back
|
||||
[06:19:56] wger: readback of the seeded weight entry http=200 found=True
|
||||
[06:19:56] ABORT: app came back in 62.0s; data present=True
|
||||
@@ -0,0 +1,58 @@
|
||||
[2K[2Kwger-files | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
|
||||
[2Kwger-files | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
|
||||
[2Kwger-files | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
|
||||
[2Kwger-files | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
|
||||
[2Kwger-files | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
|
||||
[2Kwger-files | /docker-entrypoint.sh: Configuration complete; ready for start up
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: using the "epoll" event method
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: nginx/1.30.5
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: built by gcc 15.2.0 (Alpine 15.2.0)
|
||||
wger | *** Using settings from env: settings.main
|
||||
[2K[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: OS: Linux 7.0.14-20-pve
|
||||
wger | level=INFO ts=2026-10-08 08:07:35,364 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | Running in production mode, running collectstatic now
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:07:37,454 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger |
|
||||
[2Kwger | 22725 static files deleted, 11362 static files copied to '/home/wger/static', 11362 post-processed.
|
||||
[2Kwger | Performing database migrations
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:08:06,559 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | System check identified some issues:
|
||||
[2Kwger |
|
||||
[2Kwger | WARNINGS:
|
||||
[2Kwger | ?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
[2Kwger | HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
[2Kwger | Operations to perform:
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 524288:524288
|
||||
[2Kwger | 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
|
||||
[2K[2Kwger | Running migrations:
|
||||
[2Kwger | No migrations to apply.
|
||||
[2Kwger | Your models in app(s): 'exercises', 'gallery' have changes that are not yet reflected in a migration, and so won't be applied.
|
||||
[2Kwger | Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:08:10,187 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | System check identified some issues:
|
||||
[2Kwger |
|
||||
[2Kwger | WARNINGS:
|
||||
[2Kwger | ?: (axes.W006) AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'. This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies.
|
||||
[2Kwger | HINT: Add 'ip_address' to AXES_LOCKOUT_PARAMETERS.
|
||||
[2Kwger | Set site URL to fitness.gate.invalid
|
||||
[2Kwger | Using gunicorn on port 8000...
|
||||
[2Kwger | level=INFO ts=2026-10-08 08:08:12,859 module=apps path=/home/wger/.local/lib/python3.12/site-packages/axes/apps.py line=53 message=AXES: BEGIN version 8.3.1, blocking by username
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [17] [INFO] Starting gunicorn 26.1.0
|
||||
wger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker processes
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 30
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 31
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 32
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 33
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 34
|
||||
[2Kwger-files | 2026/10/08 06:07:33 [notice] 1#1: start worker process 35
|
||||
[2Kwger-files | 127.0.0.1 - - [08/Oct/2026:06:08:03 +0000] "GET / HTTP/1.1" 200 896 "-" "Wget" "-"
|
||||
[2Kwger-files | 127.0.0.1 - - [08/Oct/2026:06:08:33 +0000] "GET / HTTP/1.1" 200 896 "-" "Wget" "-"
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [17] [INFO] Listening at: http://0.0.0.0:8000 (17)
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [17] [INFO] Using worker: sync
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [18] [INFO] Booting worker with pid: 18
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [19] [INFO] Booting worker with pid: 19
|
||||
[2Kwger | [2026-10-08 08:08:13 +0200] [17] [INFO] Control socket listening at /home/wger/.gunicorn/gunicorn.ctl
|
||||
[2Kwger | level=WARNING ts=2026-10-08 08:08:33,548 module=wsgi path=/home/wger/.local/lib/python3.12/site-packages/gunicorn/http/wsgi.py line=450 message=WSGI app sent body bytes on a no-body response (method=HEAD status=200); dropping per RFC 9110.
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"wger": {
|
||||
"status": "running",
|
||||
"health": "healthy",
|
||||
"restarts": 0,
|
||||
"exit": 0
|
||||
},
|
||||
"wger-files": {
|
||||
"status": "running",
|
||||
"health": "healthy",
|
||||
"restarts": 0,
|
||||
"exit": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"harness_version": 5,
|
||||
"edge": "MV-wger",
|
||||
"app": "wger",
|
||||
"note": "definition step to wger@gunicorn",
|
||||
"from": {
|
||||
"wger": "wger/server:2.7",
|
||||
"wger-files": "nginx:1.30.5-alpine"
|
||||
},
|
||||
"to": {
|
||||
"wger": "wger/server:2.7",
|
||||
"wger-files": "nginx:1.30.5-alpine"
|
||||
},
|
||||
"verdict": "proven",
|
||||
"seed_read_before": true,
|
||||
"seed_read_after": true,
|
||||
"healthy_after": true,
|
||||
"migration_observed": "\u001b[2Kwger | Performing database migrations",
|
||||
"abort": "starts-and-serves",
|
||||
"abort_detail": null,
|
||||
"engine_state_after": null,
|
||||
"memory": {
|
||||
"soak_s": 606.5,
|
||||
"requested_s": 600,
|
||||
"requests": 11584,
|
||||
"codes": {
|
||||
"302": 11584
|
||||
},
|
||||
"first_kill": null,
|
||||
"containers": {
|
||||
"wger": {
|
||||
"limit": 402653184,
|
||||
"peak": 402653184,
|
||||
"peak_pct": 1.0,
|
||||
"anon_peak_sampled": 178151424,
|
||||
"anon_peak_pct": 0.442,
|
||||
"swap_peak": 0,
|
||||
"oom_kills": 0,
|
||||
"restarts": 0,
|
||||
"oomkilled_flag": false,
|
||||
"measured": true
|
||||
},
|
||||
"wger-files": {
|
||||
"limit": 33554432,
|
||||
"peak": 9281536,
|
||||
"peak_pct": 0.277,
|
||||
"anon_peak_sampled": 5308416,
|
||||
"anon_peak_pct": 0.158,
|
||||
"swap_peak": 0,
|
||||
"oom_kills": 0,
|
||||
"restarts": 0,
|
||||
"oomkilled_flag": false,
|
||||
"measured": true
|
||||
}
|
||||
},
|
||||
"unmeasured": [],
|
||||
"venue_swap_bytes": 0,
|
||||
"load": "reached"
|
||||
},
|
||||
"marks": [],
|
||||
"bench_overrides": null,
|
||||
"duration_s": 62.1,
|
||||
"measured_at": "2026-10-08T06:19:56Z",
|
||||
"evidence": "evidence/MV-wger",
|
||||
"scratch_cleared": [],
|
||||
"files_changed": [],
|
||||
"files_changed_detail": [],
|
||||
"files_ignored": [],
|
||||
"total_s": 853.6
|
||||
}
|
||||
Reference in New Issue
Block a user