# 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']*name="([^"]+)"[^>]*value="([^"]*)"', html)) for sel in re.findall(r']*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, "") 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, "") w.say(app, "HOUSEHOLD SETUP through the gate ->", st, text[:120]) ro.save(b)