gates / gates (push) Successful in 7m2s
Closed R-1115, R-1111, R-1112, R-1113, R-1109, R-1110, R-1114, R-1096; opened R-1116 (decoy suites in the other runners), R-1117 (Immich/Nextcloud Redis volume, deferred). Controller 0.325.0 is the floor since 08:21 UTC. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
101 lines
6.6 KiB
Python
101 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""samba1109.py — R-1109 live on demo-hp 9201 through the dashboard's own form posts (the „Megosztás" page).
|
|
|
|
Env: SC (0600 dir: .ctlpw, and .smbpw written here — never printed), EV, TARGET=9201. Steps:
|
|
setup sharing on (server name FELHOMHP), a household SMB password, share r1109a in a NEW folder on hdd_1
|
|
read NAME smbclient //192.168.0.155/NAME from SCRATCH GUEST 9202 (a throwaway --rm felhom-samba client
|
|
container there — nothing runs on DooPlex): write, list, read back, delete; plus a wrong password
|
|
rename A B the household's rename: delete share A, add share B on the SAME folder („existing")
|
|
add NAME add a second share NAME on the same folder as r1109b
|
|
delete NAME delete a share
|
|
facts felhom-samba's Created + label, the smb.conf share sections, the compose's binds
|
|
teardown delete the test shares, sharing off, remove the test folder
|
|
"""
|
|
import os, secrets, subprocess, sys, time
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/night-restore-2026-10-17/tools")
|
|
import box_walk as w # noqa: E402
|
|
EV = os.environ["EV"]
|
|
LOG = open(os.path.join(EV, "r1109-samba-live.txt"), "a", buffering=1)
|
|
_say = w.say
|
|
def say(*a):
|
|
_say(*a); LOG.write(time.strftime("%H:%M:%S ") + " ".join(str(x) for x in a) + "\n")
|
|
w.say = say
|
|
import drill # noqa: E402 (form(): a dashboard FORM post with the session's CSRF)
|
|
import re # noqa: E402
|
|
# drill.form logs the fields it posts — a password field is redacted BEFORE the line is written anywhere (2026-10-17:
|
|
# the first run logged the throwaway test password; scrubbed, and the password replaced at teardown).
|
|
drill.say = lambda *a: say(*[re.sub(r"('smb_password(?:_confirm)?': )'[^']*'", r"\1'<redacted>'", str(x)) for x in a])
|
|
ROOT = "/mnt/felhom-drives/hdd_1"
|
|
DIR = ROOT + "/shares/r1109a"
|
|
IMG = "gitea.dooplex.hu/admin/felhom-samba:1.2.0"
|
|
|
|
def facts():
|
|
out = w.guest("""docker inspect felhom-samba -f 'samba: created {{.Created}} label={{index .Config.Labels "felhom.smb-conf-sha256"}} status={{.State.Status}}' 2>&1
|
|
d=/opt/docker/stacks/felhom-samba; [ -d $d ] || d=$(dirname $(ls /opt/docker/stacks/*/smb.conf | head -1))
|
|
echo "smb.conf shares: $(grep -E '^\\[' $d/smb.conf | tr '\\n' ' ')"
|
|
echo "smb.conf hash: $(sha256sum $d/smb.conf | cut -c1-16)… compose binds: $(grep -E '^ - /mnt' $d/docker-compose.yml | tr -s ' ' | tr '\\n' ' ')"
|
|
echo "inside the container: $(docker exec felhom-samba grep -E '^\\[' /etc/samba/smb.conf | tr '\\n' ' ')" """)
|
|
say(out.strip())
|
|
|
|
def read(name):
|
|
auth = f"/root/r1109-auth-{os.getpid()}"
|
|
pw = open(f"{w.SC}/.smbpw").read().strip()
|
|
script = (f"umask 077; printf 'username=felhom\\npassword=%s\\n' \"$(cat)\" > {auth}; "
|
|
f"printf 'username=felhom\\npassword=not-the-password\\n' > {auth}.bad; docker pull -q {IMG} >/dev/null; "
|
|
f"docker run --rm --network host -v {auth}:/auth:ro --entrypoint sh {IMG} -c "
|
|
f"\"echo r1109-{name} > /tmp/p.txt && smbclient //192.168.0.155/{name} -A /auth -m SMB3 -c 'put /tmp/p.txt probe-{name}.txt; ls probe-*; get probe-{name}.txt /tmp/b.txt; rm probe-{name}.txt' 2>&1 | grep -vE '^\\s*$|blocks of size'; echo \\\"read back: \\$(cat /tmp/b.txt 2>/dev/null || echo NOTHING)\\\"\"; "
|
|
f"echo 'wrong password:' $(docker run --rm --network host -v {auth}.bad:/auth:ro --entrypoint smbclient {IMG} //192.168.0.155/{name} -A /auth -m SMB3 -c ls 2>&1 | tail -1); "
|
|
f"rm -f {auth} {auth}.bad")
|
|
# The password travels on STDIN only: the first line is read into a shell variable on the Proxmox host and piped
|
|
# to the guest script by printf (a builtin — never an argv, never a file); the script follows on the same stdin.
|
|
r = subprocess.run(w.ssh_args() + ["umask 077; IFS= read -r PW; cat > /tmp/r1109.sh; pct push 9202 /tmp/r1109.sh /root/r1109.sh; "
|
|
"rm -f /tmp/r1109.sh; printf '%s' \"$PW\" | pct exec 9202 -- bash /root/r1109.sh; "
|
|
"pct exec 9202 -- rm -f /root/r1109.sh"],
|
|
input=pw + "\n" + script, capture_output=True, text=True, timeout=300)
|
|
say(f"read //{name} from 9202:\n" + "\n".join(l for l in (r.stdout + r.stderr).splitlines() if not l.startswith("perl:") and "locale" not in l and "LC_" not in l and "LANG" not in l and "are supported" not in l))
|
|
|
|
def main():
|
|
step, args = sys.argv[1], sys.argv[2:]
|
|
w.login()
|
|
if step == "setup":
|
|
drill.form("/sharing/enable", {"enabled": "on", "server_name": "FELHOMHP"})
|
|
pw = secrets.token_urlsafe(18)
|
|
fd = os.open(f"{w.SC}/.smbpw", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600); os.write(fd, pw.encode()); os.close(fd)
|
|
drill.form("/sharing/password", {"smb_password": pw, "smb_password_confirm": pw})
|
|
for _ in range(60):
|
|
time.sleep(5)
|
|
c, d = w.ctl("GET", "/sharing/status")
|
|
if "running" in str(d).lower() and "prepar" not in str(d).lower():
|
|
break
|
|
say("status: " + str(d)[:300])
|
|
drill.form("/sharing/shares", {"name": "r1109a", "mode": "new", "storage_root": ROOT})
|
|
time.sleep(8); facts()
|
|
elif step == "read":
|
|
read(args[0])
|
|
elif step == "rename":
|
|
a, b = args
|
|
drill.form("/sharing/shares/delete", {"name": a}); time.sleep(6)
|
|
drill.form("/sharing/shares", {"name": b, "mode": "existing", "path": DIR}); time.sleep(10); facts()
|
|
elif step == "add":
|
|
drill.form("/sharing/shares", {"name": args[0], "mode": "existing", "path": DIR}); time.sleep(10); facts()
|
|
say("controller log: " + w.guest("docker logs --since 2m felhom-controller 2>&1 | grep -iE 'samba|sharing' | grep -v DEBUG | tail -6"))
|
|
elif step == "delete":
|
|
drill.form("/sharing/shares/delete", {"name": args[0]}); time.sleep(10); facts()
|
|
elif step == "facts":
|
|
facts()
|
|
elif step == "teardown":
|
|
dead = secrets.token_urlsafe(18) # the household password replaced by an unlogged one before sharing goes off
|
|
drill.form("/sharing/password", {"smb_password": dead, "smb_password_confirm": dead})
|
|
time.sleep(5)
|
|
for n in args:
|
|
drill.form("/sharing/shares/delete", {"name": n})
|
|
drill.form("/sharing/enable", {"enabled": "off", "server_name": "FELHOMHP"})
|
|
time.sleep(10)
|
|
say("teardown: " + w.guest(f"rm -rf {DIR}; rmdir {ROOT}/shares 2>/dev/null; docker ps -a --filter name=felhom-samba --format '{{{{.Names}}}} {{{{.Status}}}}'; ls {ROOT}/shares 2>&1 | head -2"))
|
|
os.remove(f"{w.SC}/.smbpw")
|
|
else:
|
|
sys.exit("unknown step")
|
|
|
|
main()
|