Files
timmy-home/scripts/evennia/bootstrap_local_evennia.py
2026-03-28 13:33:26 -04:00

141 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
RUNTIME_ROOT = Path.home() / ".timmy" / "evennia"
GAME_DIR = RUNTIME_ROOT / "timmy_world"
VENV_DIR = RUNTIME_ROOT / "venv"
EVENNIA_BIN = VENV_DIR / "bin" / "evennia"
PYTHON_BIN = VENV_DIR / "bin" / "python3"
OPERATOR_USER = os.environ.get("TIMMY_EVENNIA_OPERATOR", "Alexander")
OPERATOR_EMAIL = os.environ.get("TIMMY_EVENNIA_OPERATOR_EMAIL", "alexpaynex@gmail.com")
OPERATOR_PASSWORD = os.environ.get("TIMMY_EVENNIA_OPERATOR_PASSWORD", "timmy-local-dev")
TIMMY_PASSWORD = os.environ.get("TIMMY_EVENNIA_TIMMY_PASSWORD", "timmy-world-dev")
def ensure_venv():
if not PYTHON_BIN.exists():
subprocess.run([sys.executable, "-m", "venv", str(VENV_DIR)], check=True)
subprocess.run([str(PYTHON_BIN), "-m", "pip", "install", "--upgrade", "pip"], check=True)
subprocess.run([str(PYTHON_BIN), "-m", "pip", "install", "evennia"], check=True)
def ensure_game_dir():
if not GAME_DIR.exists():
RUNTIME_ROOT.mkdir(parents=True, exist_ok=True)
subprocess.run([str(EVENNIA_BIN), "--init", "timmy_world"], cwd=RUNTIME_ROOT, check=True)
def run_evennia(args, env=None, timeout=600):
result = subprocess.run([str(EVENNIA_BIN), *args], cwd=GAME_DIR, env=env, timeout=timeout, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(result.stderr or result.stdout)
return result.stdout
def ensure_db_and_owner():
run_evennia(["migrate"])
env = os.environ.copy()
env.update(
{
"EVENNIA_SUPERUSER_USERNAME": OPERATOR_USER,
"EVENNIA_SUPERUSER_EMAIL": OPERATOR_EMAIL,
"EVENNIA_SUPERUSER_PASSWORD": OPERATOR_PASSWORD,
}
)
run_evennia(["shell", "-c", "from evennia.accounts.models import AccountDB; print(AccountDB.objects.count())"], env=env)
def ensure_server_started():
run_evennia(["start"], timeout=240)
def run_shell(code: str):
env = os.environ.copy()
env["PYTHONPATH"] = str(REPO_ROOT) + os.pathsep + env.get("PYTHONPATH", "")
return run_evennia(["shell", "-c", code], env=env)
def ensure_timmy_and_world():
code = f'''
import sys
sys.path.insert(0, {str(REPO_ROOT)!r})
from evennia.accounts.models import AccountDB
from evennia.accounts.accounts import DefaultAccount
from evennia import DefaultRoom, DefaultExit, create_object
from evennia.utils.search import search_object
from evennia_tools.layout import ROOMS, EXITS, OBJECTS
from typeclasses.objects import Object
acc = AccountDB.objects.filter(username__iexact="Timmy").first()
if not acc:
acc, errs = DefaultAccount.create(username="Timmy", password={TIMMY_PASSWORD!r})
room_map = {{}}
for room in ROOMS:
found = search_object(room.key, exact=True)
obj = found[0] if found else None
if obj is None:
obj, errs = DefaultRoom.create(room.key, description=room.desc)
else:
obj.db.desc = room.desc
room_map[room.key] = obj
for ex in EXITS:
source = room_map[ex.source]
dest = room_map[ex.destination]
found = [obj for obj in source.contents if obj.key == ex.key and getattr(obj, "destination", None) == dest]
if not found:
DefaultExit.create(ex.key, source, dest, description=f"Exit to {{dest.key}}.", aliases=list(ex.aliases))
for spec in OBJECTS:
location = room_map[spec.location]
found = [obj for obj in location.contents if obj.key == spec.key]
if not found:
obj = create_object(typeclass=Object, key=spec.key, location=location)
else:
obj = found[0]
obj.db.desc = spec.desc
char = list(acc.characters)[0]
char.location = room_map["Gate"]
char.home = room_map["Gate"]
char.save()
print("WORLD_OK")
print("TIMMY_LOCATION", char.location.key)
'''
return run_shell(code)
def verify():
status = run_evennia(["status"], timeout=120)
info = run_evennia(["info"], timeout=120)
web = subprocess.run(
[sys.executable, "-c", "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:4001', timeout=10); print(r.status); print(r.headers.get('Content-Type'))"],
timeout=30,
capture_output=True,
text=True,
check=True,
)
return {"status": status.strip(), "info": info.strip(), "web": web.stdout.strip()}
def main():
ensure_venv()
ensure_game_dir()
ensure_db_and_owner()
ensure_server_started()
provision = ensure_timmy_and_world()
proof = verify()
print(json.dumps({"provision_stdout": provision, "verification": proof}, indent=2))
if __name__ == "__main__":
main()