Files
felhom.eu/documentation/audits/gate-rollout-2026-09-29/B/ro_setup.py
T

191 lines
8.3 KiB
Python

# The household's first setup of each app, THROUGH the gate (its browser holds the gate cookies from `ro.py household`).
# Each app's own first-run API, as its first-setup screen calls it. Passwords from ro_creds.json; never printed.
import json, sys, time
sys.path.insert(0, '.')
import ro
from ro import w, host
def J(b, app, method, path, body=None, headers=None):
st, text, _ = b.req(f"https://{host(app)}{path}", method, body=body, accept="application/json", headers=headers)
return st, text
def actualbudget(b, c):
return J(b, "actualbudget", "POST", "/account/bootstrap", {"password": c["pw"]})
def komga(b, c):
return J(b, "komga", "POST", "/api/v1/claim", headers={"X-Komga-Email": c["email"], "X-Komga-Password": c["pw"]})
def jellyfin(b, c):
out = []
out.append(J(b, "jellyfin", "POST", "/Startup/Configuration", {"UICulture": "en-US", "MetadataCountryCode": "HU", "PreferredMetadataLanguage": "hu"})[0])
out.append(J(b, "jellyfin", "GET", "/Startup/User")[0])
out.append(J(b, "jellyfin", "POST", "/Startup/User", {"Name": c["user"], "Password": c["pw"]})[0])
st, t = J(b, "jellyfin", "POST", "/Startup/Complete")
out.append(st)
return st, f"steps {out}"
def emby(b, c):
out = [J(b, "emby", "POST", "/emby/Startup/Configuration", {"UICulture": "en-US", "MetadataCountryCode": "HU", "PreferredMetadataLanguage": "hu"})[0],
J(b, "emby", "POST", "/emby/Startup/User", {"Name": c["user"], "Password": c["pw"]})[0]]
st, t = J(b, "emby", "POST", "/emby/Startup/Complete")
out.append(st)
return st, f"steps {out}"
def navidrome(b, c):
return J(b, "navidrome", "POST", "/auth/createAdmin", {"username": c["user"], "password": c["pw"]})
def ghost(b, c):
return J(b, "ghost", "POST", "/ghost/api/admin/authentication/setup/",
{"setup": [{"name": "Family", "email": c["email"], "password": c["pw"] + "Zz9", "blogTitle": "Family"}]},
headers={"Origin": f"https://{host('ghost')}"})
def home_assistant(b, c):
return J(b, "home-assistant", "POST", "/api/onboarding/users",
{"client_id": f"https://{host('home-assistant')}/", "name": "Family", "username": c["user"], "password": c["pw"], "language": "en"})
def romm(b, c):
J(b, "romm", "GET", "/api/heartbeat") # sets romm's csrftoken cookie, as its setup screen's first call does
tok = b.jar.get(host("romm"), {}).get("romm_csrftoken") or b.jar.get(host("romm"), {}).get("csrftoken", "")
return J(b, "romm", "POST", "/api/users", {"username": c["user"], "password": c["pw"], "email": c["email"], "role": "admin"},
headers={"X-CSRFToken": tok})
def zipline(b, c):
return J(b, "zipline", "POST", "/api/setup", {"username": c["user"], "password": c["pw"]})
def docmost(b, c):
return J(b, "docmost", "POST", "/api/auth/setup", {"name": "Family", "email": c["email"], "password": c["pw"], "workspaceName": "Family"})
def calcom(b, c):
return J(b, "calcom", "POST", "/api/auth/setup", {"username": c["user"], "email_address": c["email"], "full_name": "Family", "password": c["pw"] + "Aa1!"})
def _form(b, app, path, fields, csrf_name):
import re, urllib.parse
st, html, _ = b.req(f"https://{host(app)}{path}")
m = re.search(r'name="' + csrf_name + r'" value="([^"]+)"', html)
fields[csrf_name] = m.group(1) if m else ""
body = urllib.parse.urlencode(fields)
st, text, hops = b.req(f"https://{host(app)}{path}", "POST", body=body,
headers={"Content-Type": "application/x-www-form-urlencoded", "Referer": f"https://{host(app)}{path}", "Origin": f"https://{host(app)}"})
return st, f"after POST: {' -> '.join(str(h[0]) + ' ' + h[2] for h in hops)}"
def gitea(b, c):
import re
st, html, _ = b.req(f"https://{host('gitea')}/")
fields = dict(re.findall(r'<input[^>]*name="([^"]+)"[^>]*value="([^"]*)"', html))
for sel in re.findall(r'<select[^>]*name="([^"]+)"', html):
fields.setdefault(sel, "")
fields.update({"db_type": fields.get("db_type") or "sqlite3", "admin_name": "family", "admin_email": c["email"],
"admin_passwd": c["pw"], "admin_confirm_passwd": c["pw"]})
import urllib.parse
st, text, hops = b.req(f"https://{host('gitea')}/", "POST", body=urllib.parse.urlencode(fields),
headers={"Content-Type": "application/x-www-form-urlencoded"})
return st, f"after POST: {' -> '.join(str(h[0]) + ' ' + h[2] for h in hops)}"
def tandoor(b, c):
return _form(b, "tandoor", "/setup/", {"name": c["user"], "password": c["pw"], "password_confirm": c["pw"]}, "csrfmiddlewaretoken")
def homebox(b, c):
return J(b, "homebox", "POST", "/api/v1/users/register", {"name": "Family", "email": c["email"], "password": c["pw"]})
def papra(b, c):
return J(b, "papra", "POST", "/api/auth/sign-up/email", {"email": c["email"], "password": c["pw"], "name": "Family"},
headers={"Origin": f"https://{host('papra')}"})
def sparkyfitness(b, c):
return J(b, "sparkyfitness", "POST", "/api/auth/register", {"email": c["email"], "password": c["pw"], "full_name": "Family"})
def vikunja(b, c):
return J(b, "vikunja", "POST", "/api/v1/register", {"username": c["user"], "email": c["email"], "password": c["pw"]})
def adventurelog(b, c):
J(b, "adventurelog", "GET", "/auth/browser/v1/config")
tok = b.jar.get(host("adventurelog"), {}).get("csrftoken", "")
return J(b, "adventurelog", "POST", "/auth/browser/v1/auth/signup", {"username": c["user"], "email": c["email"], "password": c["pw"]},
headers={"X-CSRFToken": tok, "Referer": f"https://{host('adventurelog')}/", "Origin": f"https://{host('adventurelog')}"})
def termix(b, c):
return J(b, "termix", "POST", "/users/create", {"username": c["user"], "password": c["pw"]})
def opengist(b, c):
return _form(b, "opengist", "/-/register", {"username": c["user"], "password": c["pw"]}, "_csrf")
def _arr(b, app, c):
import re
st, html, _ = b.req(f"https://{host(app)}/initialize.json", accept="application/json")
key = json.loads(html).get("apiKey", "")
h = {"X-Api-Key": key}
st, cfg = J(b, app, "GET", "/api/v3/config/host", headers=h)
cfg = json.loads(cfg)
cfg.update({"authenticationMethod": "forms", "authenticationRequired": "enabled", "username": c["user"],
"password": c["pw"], "passwordConfirmation": c["pw"]})
st, t = J(b, app, "PUT", "/api/v3/config/host", cfg, headers=h)
return st, t[:40].replace(key, "<key>")
def radarr(b, c):
return _arr(b, "radarr", c)
def sonarr(b, c):
return _arr(b, "sonarr", c)
def gramps_web(b, c):
st, t = J(b, "gramps-web", "GET", "/api/token/create_owner/")
tok = json.loads(t).get("access_token", "") if st == 200 else ""
st, t = J(b, "gramps-web", "POST", "/api/users/" + c["user"] + "/create_owner/", {"password": c["pw"], "email": c["email"], "full_name": "Family"},
headers={"Authorization": "Bearer " + tok})
return st, t
def wishlist(b, c):
import urllib.parse
body = urllib.parse.urlencode({"name": "Family", "username": c["user"], "email": c["email"], "password": c["pw"], "confirmPassword": c["pw"]})
st, t, _ = b.req(f"https://{host('wishlist')}/signup", "POST", body=body, accept="application/json",
headers={"Content-Type": "application/x-www-form-urlencoded", "Origin": f"https://{host('wishlist')}", "x-sveltekit-action": "true"})
return st, t
FN = {"actualbudget": actualbudget, "komga": komga, "jellyfin": jellyfin, "emby": emby, "navidrome": navidrome,
"ghost": ghost, "home-assistant": home_assistant, "romm": romm,
"zipline": zipline, "docmost": docmost, "calcom": calcom, "gitea": gitea, "tandoor": tandoor,
"homebox": homebox, "papra": papra, "sparkyfitness": sparkyfitness, "vikunja": vikunja, "adventurelog": adventurelog,
"termix": termix, "opengist": opengist, "radarr": radarr, "sonarr": sonarr, "gramps-web": gramps_web, "wishlist": wishlist}
if __name__ == "__main__":
w.login()
b = ro.household()
for app in sys.argv[1:]:
c = ro.creds(app)
try:
st, text = FN[app](b, c)
except Exception as e:
st, text = "ERR", str(e)
for v in (c["pw"],):
text = str(text).replace(v, "<pw>")
w.say(app, "HOUSEHOLD SETUP through the gate ->", st, text[:120])
ro.save(b)