2d69c61556
gates / gates (push) Successful in 27s
The operator's request of 2026-10-01 recorded in CONTEXT with the reviewer's defaults (new apps only; the 53 get a read-only gap page). The pilot: the draft caught R-752 and R-755, missed R-737 and (for a new app) R-738; the sharpened and new rows then found R-762 (wger serves no static files or photos), R-763 (strangers sign up, guest accounts), R-764 (no mail). Also R-758 (8 mem_limit under the sum), R-759 (wger's open record rows), R-760 (vikunja healthcheck), R-761 (logo comment). R-755 note. Register 392 -> 399. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
373 lines
19 KiB
Python
373 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""Part C, the pilot: measure on scratch guest 9202 what wger's onboarding record ("as it is now") has no evidence for.
|
|
|
|
argv[1] = drill | walk | restore
|
|
drill — point 9202 at the drill catalog (`09` §6.5; R-615: the cache dir goes too), save the config first
|
|
walk — one fresh wger install from the drill catalog and the reads below; then remove it through the product
|
|
restore — put 9202 back on the saved config (the live catalog) and print the controls
|
|
|
|
Reads (each names the checklist row it answers):
|
|
0.4/0.5/1.7/1.8 the image's own env defaults, the container's env NAMES (values never printed except the named
|
|
non-secret switches), Django's DEBUG, the settings' env reads
|
|
1.5 the server process inside the container
|
|
3.5 the default login polled once a second from the install press, through traefik, as a stranger
|
|
4.3 press → healthy, RestartCount
|
|
5.1 the wger container's cgroup from its birth, every 2 s: anon, swap, oom_kill (swap reported, so a
|
|
pass on swap is visible)
|
|
3.9/0.7 the browser form (https Origin + CSRF) and the phone app's route, right and wrong
|
|
1.8 an unknown page through traefik: a Django debug page or not
|
|
2.8 a progress photo uploaded through the API and fetched back through traefik
|
|
2.7 the volumes' size after the seed
|
|
4.4 negative control: the container paused → does the controller read it unhealthy?
|
|
2.6 remove through the product → what is left
|
|
Secrets: the generated admin password lives in this process and a 0600 file in SC only; nothing prints it.
|
|
"""
|
|
import json, os, re, sys, time, tempfile, secrets, base64, io, subprocess
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
import box_walk as w
|
|
import upgrade_fixtures_box as fxb
|
|
|
|
EV = os.environ["EV"]
|
|
VOL = "/var/lib/docker/volumes/felhom-controller-data/_data"
|
|
DRILL_REPO = "https://gitea.dooplex.hu/admin/app-catalog-drill.git"
|
|
SAVE = f"{VOL}/controller.yaml.pre-checklist1001"
|
|
SUB = "fitness"
|
|
OUT = []
|
|
|
|
|
|
def p(*a):
|
|
line = " ".join(str(x) for x in a)
|
|
print(line, flush=True)
|
|
OUT.append(line)
|
|
|
|
|
|
def dump(name):
|
|
with io.open(os.path.join(EV, name), "w", encoding="utf-8") as fh:
|
|
fh.write("\n".join(OUT) + "\n")
|
|
|
|
|
|
def creds():
|
|
for l in io.open(os.path.expanduser("~/.git-credentials")).read().strip().split("\n"):
|
|
m = re.match(r'https://(admin):([^@]+)@gitea\.dooplex\.hu', l)
|
|
if m:
|
|
return m.group(1), m.group(2)
|
|
raise SystemExit("no admin credential")
|
|
|
|
|
|
def to_drill():
|
|
u, t = creds()
|
|
p(w.guest(f"""
|
|
set -e
|
|
test -f {SAVE} || cp -p {VOL}/controller.yaml {SAVE}
|
|
python3 - <<'PY'
|
|
import re
|
|
p = "{VOL}/controller.yaml"
|
|
s = open(p).read()
|
|
s = re.sub(r'(^\\s+repo_url: ).*$', r'\\g<1>{DRILL_REPO}', s, count=1, flags=re.M)
|
|
s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+token: ).*$', r'\\g<1>"{t}"', s, count=1, flags=re.M)
|
|
s = re.sub(r'(^git:(?:\\n\\s+.*)*?\\n\\s+username: ).*$', r'\\g<1>"{u}"', s, count=1, flags=re.M)
|
|
open(p, "w").write(s)
|
|
PY
|
|
rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache
|
|
docker restart felhom-controller >/dev/null
|
|
sleep 20
|
|
echo "saved copy: $(ls -l {SAVE} | awk '{{print $5, $9}}')"
|
|
grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: <redacted>/'
|
|
"""))
|
|
w.login()
|
|
w.sync_rescan()
|
|
controls("drill")
|
|
dump("C0-repoint-drill.txt")
|
|
|
|
|
|
def controls(want):
|
|
head = w.guest(f"cd {VOL}/data/catalog-cache 2>/dev/null && git log --oneline -1 && git remote get-url origin | sed 's#//[^@]*@#//#'").strip()
|
|
p("CONTROL 1 — the box's catalog clone:", head)
|
|
live = subprocess.run(["git", "ls-remote", "https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git", "refs/heads/main"],
|
|
capture_output=True, text=True).stdout.split()[:1]
|
|
p("CONTROL 2 — the LIVE catalog main (unchanged by this act):", live)
|
|
p("CONTROL 3 — expected source:", want)
|
|
|
|
|
|
def restore():
|
|
p(w.guest(f"""
|
|
set -e
|
|
cp -p {SAVE} {VOL}/controller.yaml
|
|
rm -rf {VOL}/catalog-cache {VOL}/data/catalog-cache
|
|
docker restart felhom-controller >/dev/null
|
|
sleep 20
|
|
grep -A6 '^git:' {VOL}/controller.yaml | sed 's/token:.*/token: <redacted>/'
|
|
"""))
|
|
w.login()
|
|
w.sync_rescan()
|
|
controls("live")
|
|
p("leftovers named wger:", w.guest("docker ps -a --format '{{.Names}}' | grep -i wger; docker volume ls -q | grep -i wger; ls -d /opt/docker/stacks/wger 2>/dev/null").strip() or "none")
|
|
dump("C9-restore-live.txt")
|
|
|
|
|
|
SAMPLER = r"""
|
|
cat > /root/wger-sampler.sh <<'SH'
|
|
#!/bin/bash
|
|
out=/root/wger-mem.csv; : > $out
|
|
t0=$(date +%s)
|
|
id=""
|
|
while [ $(( $(date +%s) - t0 )) -lt 1200 ]; do
|
|
if [ -z "$id" ]; then id=$(docker ps -aq --no-trunc --filter name=^wger$ | head -1); fi
|
|
if [ -n "$id" ]; then
|
|
cg=$(ls -d /sys/fs/cgroup/system.slice/docker-$id.scope 2>/dev/null || find /sys/fs/cgroup -maxdepth 4 -type d -name "*$id*" 2>/dev/null | head -1)
|
|
if [ -n "$cg" ] && [ -f "$cg/memory.stat" ]; then
|
|
anon=$(awk '$1=="anon"{print $2}' $cg/memory.stat)
|
|
cur=$(cat $cg/memory.current); sw=$(cat $cg/memory.swap.current 2>/dev/null || echo NA)
|
|
oom=$(awk '$1=="oom_kill"{print $2}' $cg/memory.events); lim=$(cat $cg/memory.max)
|
|
rc=$(docker inspect -f '{{.RestartCount}} {{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' $id 2>/dev/null)
|
|
echo "$(date +%s.%N | cut -c1-14),$anon,$cur,$sw,$oom,$lim,$rc" >> $out
|
|
fi
|
|
fi
|
|
[ -f /root/wger-sampler.stop ] && break
|
|
sleep 2
|
|
done
|
|
SH
|
|
chmod +x /root/wger-sampler.sh; rm -f /root/wger-sampler.stop
|
|
nohup /root/wger-sampler.sh >/dev/null 2>&1 &
|
|
echo sampler started
|
|
"""
|
|
|
|
POLLER = r"""
|
|
cat > /root/wger-poll.sh <<'SH'
|
|
#!/bin/bash
|
|
# a STRANGER: no dashboard session, no gate cookie — the public default login, once a second, through traefik
|
|
out=/root/wger-poll.txt; : > $out
|
|
t0=$(date +%s)
|
|
while [ $(( $(date +%s) - t0 )) -lt 600 ]; do
|
|
r=$(curl -sk --max-time 4 -o /root/wger-poll.body -w '%{http_code}' -H 'Host: fitness.enkisfelhom.hu' \
|
|
-H 'Content-Type: application/json' --data '{"username":"admin","password":"adminadmin"}' \
|
|
https://127.0.0.1/allauth/app/v1/auth/login)
|
|
b=$(head -c 90 /root/wger-poll.body | tr '\n' ' ')
|
|
echo "$(date +%H:%M:%S) $r $b" >> $out
|
|
# the first answer that is the APP's own JSON (not the gate, not traefik's 404) ends the poll: every further
|
|
# wrong try would count toward wger's 5-failure lock (decision 58) and spoil the household's login below
|
|
case "$b" in *'"status"'*|*access_token*) break;; esac
|
|
sleep 1
|
|
done
|
|
SH
|
|
chmod +x /root/wger-poll.sh
|
|
nohup /root/wger-poll.sh >/dev/null 2>&1 &
|
|
echo poller started
|
|
"""
|
|
|
|
|
|
def walk():
|
|
w.login()
|
|
p("##### wger on 9202 — controller", w.guest("docker inspect felhom-controller --format {{.Config.Image}}").strip())
|
|
p("catalog clone:", w.guest(f"cd {VOL}/data/catalog-cache && git log --oneline -1").strip())
|
|
p("before: stack/volumes named wger:", w.guest("ls -d /opt/docker/stacks/wger 2>/dev/null; docker volume ls -q | grep -i wger").strip() or "none")
|
|
p("guest swap (free -m):", " ".join(w.guest("free -m | grep -i swap").split()))
|
|
p(w.guest(SAMPLER).strip())
|
|
p(w.guest(POLLER).strip())
|
|
t_press = time.time()
|
|
p("deploy press at", time.strftime("%H:%M:%S"))
|
|
ok = w.deploy("wger", SUB)
|
|
p("deploy ->", ok)
|
|
opened = None
|
|
for _ in range(120):
|
|
time.sleep(5)
|
|
lg = w.guest("docker logs --since 30m felhom-controller 2>&1 | grep -E 'wger: install hold OPENED|wger.*after_install' | tail -3")
|
|
if "hold OPENED" in lg:
|
|
opened = lg.strip()
|
|
break
|
|
p("controller log (after_install / hold):", opened)
|
|
healthy = None
|
|
for _ in range(90):
|
|
h = w.guest("docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}} {{.RestartCount}}' wger").strip()
|
|
if h.startswith("healthy"):
|
|
healthy = time.time() - t_press
|
|
break
|
|
time.sleep(2)
|
|
p("4.3 press -> docker healthy: %.0f s; RestartCount: %s; start_period in compose: %s" % (
|
|
healthy or -1, w.guest("docker inspect -f '{{.RestartCount}}' wger").strip(),
|
|
w.guest("grep -m1 start_period /opt/docker/stacks/wger/docker-compose.yml").strip()))
|
|
p(" controller state:", w.stack("wger").get("state"))
|
|
time.sleep(8)
|
|
w.guest("pkill -f wger-poll.sh || true")
|
|
poll = w.guest("cat /root/wger-poll.txt").strip().splitlines()
|
|
codes = {}
|
|
for l in poll:
|
|
c = l.split(" ")[1] if len(l.split(" ")) > 1 else "?"
|
|
codes[c] = codes.get(c, 0) + 1
|
|
p("3.5 stranger polls of admin/adminadmin from the press: %d tries; codes %s" % (len(poll), codes))
|
|
p(" first 3:", poll[:3])
|
|
p(" last 3:", poll[-3:])
|
|
p(" any 200 with a token:", any(" 200 " in l and "access_token" in l for l in poll))
|
|
dump("C1-install.txt")
|
|
|
|
# --- the static reads inside the container
|
|
OUT.clear()
|
|
p("1.5 the server process:", " | ".join(w.guest("docker exec wger sh -c \"ps -eo args 2>/dev/null || cat /proc/[0-9]*/cmdline | tr '\\\\0' ' '\" | grep -E '[m]anage.py|[g]unicorn|[u]vicorn|[d]aphne' | head -4").strip().splitlines()))
|
|
p("1.7 image entrypoint:", w.guest("docker image inspect wger/server:2.7 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'").strip())
|
|
p("1.7 entrypoint switches (if-tests in /home/wger/entrypoint.sh):")
|
|
p(w.guest("docker exec wger grep -nE 'if \\[\\[? *\"?\\$' /home/wger/entrypoint.sh").rstrip())
|
|
p("0.4/1.7 the IMAGE's env defaults for those switches (+ debug/mail/sync):")
|
|
env_img = w.guest("docker image inspect wger/server:2.7 --format '{{range .Config.Env}}{{println .}}{{end}}'")
|
|
keep = re.compile(r"^(DJANGO_DEBUG|DJANGO_PERFORM_MIGRATIONS|WGER_USE_GUNICORN|SYNC_\w+|DOWNLOAD_\w+|LOAD_ONLINE\w*|"
|
|
r"DJANGO_COLLECTSTATIC\w*|ENABLE_EMAIL|EXERCISE\w*|ALLOW_\w+|USE_CELERY|DJANGO_CLEAR\w*|YARN\w*|TZ)=")
|
|
for l in env_img.splitlines():
|
|
if keep.match(l):
|
|
p(" image:", l)
|
|
p(" the RUNNING container's env, the same switches (values: only these named, non-secret ones):")
|
|
env_run = w.guest("docker inspect wger --format '{{range .Config.Env}}{{println .}}{{end}}'")
|
|
for l in env_run.splitlines():
|
|
if keep.match(l) or l.startswith(("AXES_", "CSRF_TRUSTED", "X_FORWARDED", "SITE_URL")):
|
|
p(" container:", l)
|
|
names = sorted({l.split("=", 1)[0] for l in env_run.splitlines() if "=" in l})
|
|
p(" container env NAMES (all):", " ".join(names))
|
|
p("1.6 the env names wger's settings READ (settings/main.py, env.*('NAME'...)):")
|
|
reads = w.guest("docker exec wger sh -c \"grep -ohE \\\"env\\\\.[a-z]+\\\\('[A-Z_0-9]+'\\\" /home/wger/src/settings/*.py | sed -E \\\"s/.*\\\\('//; s/'$//\\\" | sort -u\"").split()
|
|
p(" ", " ".join(reads))
|
|
secretish = [r for r in reads if re.search(r"KEY|SECRET|TOKEN|PASSWORD|PEM", r)]
|
|
p(" of which key/secret-like:", secretish)
|
|
p(" set in the container:", [r for r in secretish if r in names], " NOT set:", [r for r in secretish if r not in names])
|
|
dj = w.guest("""cat > /tmp/djs.py <<'PY'
|
|
import os, sys
|
|
sys.path.insert(0, '/home/wger/src'); os.chdir('/home/wger/src')
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.main')
|
|
import django; django.setup()
|
|
from django.conf import settings as s
|
|
print('DEBUG', s.DEBUG)
|
|
print('EMAIL_BACKEND', getattr(s, 'EMAIL_BACKEND', None))
|
|
print('WGER_SETTINGS sync/download:', {k: v for k, v in getattr(s, 'WGER_SETTINGS', {}).items() if any(x in k for x in ('SYNC', 'DOWNLOAD', 'USE_CELERY', 'ALLOW', 'EMAIL', 'TWITTER', 'MASTODON'))})
|
|
print('JWT key loaded:', bool(os.environ.get('JWT_PRIVATE_KEY')), '(env in this shell is not the server env)')
|
|
PY
|
|
docker cp /tmp/djs.py wger:/tmp/djs.py && docker exec wger python3 /tmp/djs.py 2>&1 | tail -4""")
|
|
p("1.8/0.4 Django settings inside the app:\n" + dj.rstrip())
|
|
p("0.4/0.5 the container's first-start log lines about the network (sync/download/http):")
|
|
p(w.guest("docker logs wger 2>&1 | grep -iE 'sync|download|http|fixture|ingredient|exercise' | head -12").rstrip())
|
|
p(" outbound TCP connections the wger process holds right now:", w.guest(
|
|
"docker exec wger sh -c 'cat /proc/net/tcp /proc/net/tcp6 2>/dev/null' | awk 'NR>1 && $4==\"01\"{print $3}' | sort | uniq -c | head").strip() or "none")
|
|
dump("C2-static-reads.txt")
|
|
|
|
# --- logins, as each client does
|
|
OUT.clear()
|
|
pw = (w.GENERATED.get("wger") or {}).get("ADMIN_PASSWORD") or ""
|
|
with io.open(os.path.join(os.environ["SC"], "wger.pw"), "w") as fh:
|
|
fh.write(pw)
|
|
os.chmod(os.path.join(os.environ["SC"], "wger.pw"), 0o600)
|
|
W = fxb.Wger()
|
|
jar = tempfile.mktemp(prefix="wger-jar-")
|
|
hdr, why = W._login(w, SUB, pw, jar)
|
|
p("3.9 browser form, https Origin + CSRF, RIGHT password:", why)
|
|
jar2 = tempfile.mktemp(prefix="wger-jar2-")
|
|
hdr2, why2 = W._login(w, SUB, pw + "x", jar2)
|
|
p("3.9 browser form, WRONG password:", why2, "(refused)" if hdr2 is None else "(LET IN!)")
|
|
for label, pwd in (("RIGHT", pw), ("WRONG", pw + "y")):
|
|
rc, code, out = w.app_curl(SUB, "/allauth/app/v1/auth/login", "-H", "Content-Type: application/json",
|
|
data=json.dumps({"username": "admin", "password": pwd}), method="POST")
|
|
keys = []
|
|
try:
|
|
keys = sorted((json.loads(out).get("meta") or {}).keys()) or sorted(json.loads(out).keys())
|
|
except Exception:
|
|
pass
|
|
p(f"3.9/0.7 phone-app route /allauth/app/v1/auth/login, {label} password: http={code} keys={keys}")
|
|
if label == "RIGHT" and code == "200":
|
|
tok = (json.loads(out).get("meta") or {}).get("access_token") or ""
|
|
rc, c2, _ = w.app_curl(SUB, "/api/v2/weightentry/", "-H", f"Authorization: Bearer {tok}")
|
|
p(" the API with that token: http=%s" % c2)
|
|
# --- 1.8 an unknown page
|
|
rc, code, body = w.app_curl(SUB, "/felhom-no-such-page-xyz/")
|
|
p("1.8 unknown page through traefik: http=%s debug-page=%s traceback=%s" % (
|
|
code, "DEBUG = True" in body, "Traceback" in body))
|
|
dump("C3-logins.txt")
|
|
|
|
# --- seed (the fixture's own front door) + a photo
|
|
OUT.clear()
|
|
say = lambda *a: p(*a)
|
|
t = W.seed(w, SUB, say)
|
|
p("seed:", {k: v for k, v in (t or {}).items() if k != "pw"})
|
|
png = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
|
|
fn = tempfile.mktemp(suffix=".png")
|
|
open(fn, "wb").write(png)
|
|
if hdr:
|
|
rc, code, out = w.app_curl(SUB, "/api/v2/gallery/", *hdr, "-F", f"image=@{fn};type=image/png",
|
|
"-F", "date=2026-10-01", "-F", "description=felhom pilot", method="POST")
|
|
p("2.8 POST /api/v2/gallery/ (multipart, through traefik): http=%s %s" % (code, re.sub(r'"image":"[^"]*"', '"image":"…"', out[:160])))
|
|
try:
|
|
url = json.loads(out).get("image") or ""
|
|
except Exception:
|
|
url = ""
|
|
if url:
|
|
path = re.sub(r"^https?://[^/]+", "", url)
|
|
rc, code, body = w.app_curl(SUB, path, *hdr)
|
|
p("2.8 GET the uploaded image back through traefik (%s): http=%s bytes=%d same=%s" % (
|
|
path, code, len(body.encode("latin-1", "ignore")), "PNG" in body[:8]))
|
|
rc, code, body = w.app_curl(SUB, path)
|
|
p("2.8 the same image with NO session: http=%s" % code)
|
|
p("verify (fixture readback, with its negative controls):", W.verify(w, SUB, t, say) if t else "no seed")
|
|
p("2.7 the volumes' size after the seed:", " ".join(w.guest(
|
|
"for v in $(docker volume ls -q | grep -i wger); do du -sh $(docker volume inspect -f '{{.Mountpoint}}' $v) | awk -v v=$v '{print v\"=\"$1}'; done").split()))
|
|
dump("C4-seed-photo-size.txt")
|
|
|
|
# --- 4.4 negative control
|
|
OUT.clear()
|
|
p("4.4 negative control: docker pause wger at", time.strftime("%H:%M:%S"), "— controller state before:", w.stack("wger").get("state"))
|
|
w.guest("docker pause wger")
|
|
seen = []
|
|
for i in range(45):
|
|
time.sleep(4)
|
|
st = w.stack("wger")
|
|
s = (st.get("state"), (st.get("health") or {}).get("status") if isinstance(st.get("health"), dict) else st.get("health"))
|
|
if not seen or seen[-1][1] != s:
|
|
seen.append((round(4 * (i + 1)), s))
|
|
if s[0] in ("unhealthy", "degraded", "stopped", "error"):
|
|
break
|
|
p(" states seen while paused (s, (state, health)):", seen)
|
|
w.guest("docker unpause wger")
|
|
back = None
|
|
for i in range(45):
|
|
time.sleep(4)
|
|
if w.stack("wger").get("state") == "running":
|
|
back = 4 * (i + 1)
|
|
break
|
|
p(" after unpause, state running again after %s s" % back)
|
|
dump("C5-negative-control.txt")
|
|
|
|
# --- 5.1 the sampler
|
|
OUT.clear()
|
|
w.guest("touch /root/wger-sampler.stop")
|
|
time.sleep(3)
|
|
csv = w.guest("cat /root/wger-mem.csv").strip().splitlines()
|
|
rows = [l.split(",") for l in csv if l.count(",") >= 6]
|
|
if rows:
|
|
anon = [int(r[1]) for r in rows if r[1].isdigit()]
|
|
sw = [int(r[3]) for r in rows if r[3].isdigit()]
|
|
oom = [int(r[4]) for r in rows if r[4].isdigit()]
|
|
lim = rows[0][5]
|
|
p("5.1 wger cgroup from birth: %d samples over %.0f s; limit %s" % (len(rows), float(rows[-1][0]) - float(rows[0][0]), lim))
|
|
p(" peak anon %.1f MiB (%.1f %% of the limit); peak swap %s bytes; oom_kill max %s; last: %s" % (
|
|
max(anon) / 1048576, 100.0 * max(anon) / int(lim) if lim.isdigit() else -1, max(sw) if sw else "NA", max(oom) if oom else "NA", rows[-1][6]))
|
|
with io.open(os.path.join(EV, "C6-wger-mem.csv"), "w") as fh:
|
|
fh.write("epoch,anon,current,swap,oom_kill,limit,restarts status health\n" + "\n".join(csv) + "\n")
|
|
dump("C6-first-start-memory.txt")
|
|
|
|
# --- 2.6 remove through the product
|
|
OUT.clear()
|
|
c1, d1 = w.ctl("POST", "/api/stacks/wger/stop")
|
|
p("2.6 stop ->", c1)
|
|
for _ in range(24):
|
|
time.sleep(5)
|
|
if w.stack("wger").get("state") != "running":
|
|
break
|
|
code, d = w.ctl("POST", "/api/stacks/wger/remove", {"remove_hdd_data": False, "remove_backups": True})
|
|
p("2.6 remove, KEEP drive data (wger has no drive data) ->", code, str(d)[:200])
|
|
time.sleep(6)
|
|
p(" left after: stack dir / containers / volumes:", w.guest(
|
|
"ls -d /opt/docker/stacks/wger 2>/dev/null; docker ps -a --format '{{.Names}}' | grep -x wger; docker volume ls -q | grep -i wger").strip() or "nothing")
|
|
w.guest("rm -f /root/wger-sampler.sh /root/wger-poll.sh /root/wger-poll.body /root/wger-sampler.stop /tmp/djs.py; docker exec felhom-controller true")
|
|
p(" the image (kept per decision 53 until the sweep):", w.guest("docker images --format '{{.Repository}}:{{.Tag}}' | grep -i wger").strip())
|
|
dump("C7-remove.txt")
|
|
for j in (jar, jar2, fn):
|
|
if os.path.exists(j):
|
|
os.unlink(j)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
{"drill": to_drill, "walk": walk, "restore": restore}[sys.argv[1]]()
|